[
  {
    "path": ".cargo/config.toml",
    "content": "[target.x86_64-pc-windows-msvc]\nrustflags = [\"-C\", \"link-arg=/STACK:8000000\"]\n"
  },
  {
    "path": ".claude/COMPONENT_TEST_RULES.md",
    "content": "# GPUI Component Testing Rules\n\n## Testing Principles\n\n### 1. **Simplicity First**\n\n- Avoid excessive simple tests\n- Focus on complex logic and core functionality\n\n### 2. **Builder Pattern Testing**\n\n- Every component should have a `test_*_builder` test for coverage of the builder pattern\n- Tests should cover all major configuration options\n- Use method chaining to demonstrate complete API usage\n\n#### Example:\n\n```rust\n#[gpui::test]\nfn test_button_builder(_cx: &mut gpui::TestAppContext) {\n    let button = Button::new(\"complex-button\")\n        .label(\"Save Changes\")\n        .primary()\n        .outline()\n        .large()\n        .tooltip(\"Click to save\")\n        .compact()\n        .loading(false)\n        .disabled(false)\n        .selected(false)\n        .on_click(|_, _, _| {});\n\n    // Assert all key properties\n    assert_eq!(button.label, Some(\"Save Changes\".into()));\n    assert_eq!(button.variant, ButtonVariant::Primary);\n    assert!(button.outline);\n    assert_eq!(button.size, Size::Large);\n}\n```\n\n### 3. **Complex Logic Testing**\n\n- Test conditional branching logic\n- Test state transitions and interactions\n- Test edge cases\n\n#### Example:\n\n```rust\n#[gpui::test]\nfn test_button_clickable_logic(_cx: &mut gpui::TestAppContext) {\n    // Test behavior under multiple conditions\n    let clickable = Button::new(\"test\").on_click(|_, _, _| {});\n    assert!(clickable.clickable());\n\n    let disabled = Button::new(\"test\").disabled(true).on_click(|_, _, _| {});\n    assert!(!disabled.clickable());\n\n    let loading = Button::new(\"test\").loading(true).on_click(|_, _, _| {});\n    assert!(!loading.clickable());\n}\n```\n\n### 4. **Helper Method Testing**\n\n- Test component helper methods and validation logic\n- Combine related tests into a single function\n\n#### Example:\n\n```rust\n#[gpui::test]\nfn test_button_variant_methods(_cx: &mut gpui::TestAppContext) {\n    // Test variant check methods\n    assert!(ButtonVariant::Link.is_link());\n    assert!(ButtonVariant::Text.is_text());\n    assert!(ButtonVariant::Ghost.is_ghost());\n\n    // Test related logic\n    assert!(ButtonVariant::Link.no_padding());\n    assert!(ButtonVariant::Text.no_padding());\n}\n```\n\n## What NOT to Test\n\n### ❌ Anti-patterns to Avoid\n\n1. **Simple getter/setter tests**\n\n```rust\n// ❌ Don't write tests like this\n#[gpui::test]\nfn test_button_with_label(_cx: &mut gpui::TestAppContext) {\n    let button = Button::new(\"test\").label(\"Click Me\");\n    assert_eq!(button.label, Some(\"Click Me\".into()));\n}\n```\n\n2. **Individual property tests**\n\n```rust\n// ❌ Don't write separate tests for each property\n#[gpui::test]\nfn test_button_disabled(_cx: &mut gpui::TestAppContext) {\n    let button = Button::new(\"test\").disabled(true);\n    assert!(button.disabled);\n}\n\n#[gpui::test]\nfn test_button_selected(_cx: &mut gpui::TestAppContext) {\n    let button = Button::new(\"test\").selected(true);\n    assert!(button.selected);\n}\n```\n\n_These should be merged into the builder pattern test_\n\n3. **Individual size/variant tests**\n\n```rust\n// ❌ Don't write separate tests for each size\n#[gpui::test]\nfn test_button_xsmall(_cx: &mut gpui::TestAppContext) {\n    let button = Button::new(\"test\").xsmall();\n    assert_eq!(button.size, Size::XSmall);\n}\n\n#[gpui::test]\nfn test_button_small(_cx: &mut gpui::TestAppContext) {\n    let button = Button::new(\"test\").small();\n    assert_eq!(button.size, Size::Small);\n}\n```\n\n## Test File Structure\n\n```rust\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    // 1. Builder pattern test (required)\n    #[gpui::test]\n    fn test_component_builder(_cx: &mut gpui::TestAppContext) {\n        // Test complete method chaining\n    }\n\n    // 2. Complex logic test (if applicable)\n    #[gpui::test]\n    fn test_component_complex_logic(_cx: &mut gpui::TestAppContext) {\n        // Test conditional branches, state transitions, etc.\n    }\n\n    // 3. Helper method test (if applicable)\n    #[gpui::test]\n    fn test_component_helper_methods(_cx: &mut gpui::TestAppContext) {\n        // Test helper methods\n    }\n}\n```\n\n## Test Count Guidelines\n\n| Component Type    | Recommended Tests | Notes                            |\n| ----------------- | ----------------- | -------------------------------- |\n| Simple component  | 1-2 tests         | Builder + complex logic (if any) |\n| Medium component  | 2-3 tests         | Builder + logic + helper methods |\n| Complex component | 3-5 tests         | Based on actual complexity       |\n\n## Real-world Examples\n\n### Button Component (3 tests)\n\n- `test_button_builder` - Complete configuration test\n- `test_button_clickable_logic` - Click logic test\n- `test_button_variant_methods` - Variant method test\n\n### ButtonIcon Component (2 tests)\n\n- `test_button_icon_builder` - Complete configuration test\n- `test_button_icon_variant_types` - Variant type test\n\n### ButtonGroup Component (1 test)\n\n- `test_button_group_builder` - Complete configuration test (covers all important features)\n\n### DropdownButton Component (1 test)\n\n- `test_dropdown_button_builder` - Complete configuration test\n\n### Toggle Component (2 tests)\n\n- `test_toggle_builder` - Toggle configuration test\n- `test_toggle_group_builder` - ToggleGroup configuration test\n\n## GPUI Test Usage\n\n### When to Use `#[gpui::test]`\n\n- When testing UI component rendering\n- When testing window-dependent behavior\n- When testing interactive elements that require event handling\n\n### When NOT to Use `#[gpui::test]`\n\n- For pure logic tests that don't involve rendering\n- For utility function tests\n- For simple data structure tests\n- For validation logic that doesn't require app context\n\n#### Example:\n\n```rust\n// ✅ Use regular Rust test for simple logic\n#[test]\nfn test_button_variant_conversion() {\n    let rounded: ButtonRounded = px(5.0).into();\n    assert!(matches!(rounded, ButtonRounded::Size(_)));\n}\n\n// ✅ Use gpui::test for component behavior\n#[gpui::test]\nfn test_button_builder(_cx: &mut gpui::TestAppContext) {\n    let button = Button::new(\"test\").large();\n    assert_eq!(button.size, Size::Large);\n}\n```\n\n## Summary\n\n✅ **DO**:\n\n- Test complete builder patterns\n- Test complex business logic\n- Test conditional branches and state transitions\n- Combine related tests\n- Use regular `#[test]` when GPUI context is not needed\n\n❌ **DON'T**:\n\n- Test simple property setters\n- Write separate tests for each property/size/variant\n- Test obvious functionality\n- Over-fragment tests\n- Use `#[gpui::test]` unnecessarily\n\n**Goal**: Cover the most critical functionality with minimal tests while keeping code clean and maintainable.\n"
  },
  {
    "path": ".claude/skills/generate-component-documentation/SKILL.md",
    "content": "---\nname: generate-component-documentation\ndescription: Generate documentation for new components. Use when writing docs, documenting components, or creating component documentation.\n---\n\n## Instructions\n\nWhen generating documentation for a new component:\n\n1. **Follow existing patterns**: Use the documentation styles found in the `docs` folder (examples: `button.md`, `accordion.md`, etc.)\n2. **Reference implementations**: Base the documentation on the same-named story implementation in `crates/story/src/stories`\n3. **API references**: Use markdown `code` blocks with links to docs.rs for component API references when applicable\n\n## Examples\n\nThe generated documentation should include:\n- Component description and purpose\n- Props/API documentation\n- Usage examples\n- Visual examples (if applicable)\n"
  },
  {
    "path": ".claude/skills/generate-component-story/SKILL.md",
    "content": "---\nname: generate-component-story\ndescription: Create story examples for components. Use when writing stories, creating examples, or demonstrating component usage.\n---\n\n## Instructions\n\nWhen creating component stories:\n\n1. **Follow existing patterns**: Base stories on the styles found in `crates/story/src/stories` (examples: `tabs_story.rs`, `group_box_story.rs`, etc.)\n2. **Use sections**: Organize the story with `section!` calls for each major part\n3. **Comprehensive coverage**: Include all options, variants, and usage examples of the component\n\n## Examples\n\nA typical story structure includes:\n- Basic usage examples\n- Different variants and states\n- Interactive examples\n- Edge cases and error states\n"
  },
  {
    "path": ".claude/skills/github-pull-request-description/SKILL.md",
    "content": "---\nname: github-pull-request-description\ndescription: Write a description to description GitHub Pull Request.\n---\n\n## Description\n\nWe less than 150 words description for a PR changes, including new features, bug fixes, and improvements. And if there have APIs break changes (Only `crates/ui` changes) we should have a section called `## Breaking Changes` to list them clearly.\n\n## Breaking changes description\n\nWhen a pull request introduces breaking changes to a codebase, it's important to clearly communicate these changes to users and developers who rely on the code. A well-written breaking changes description helps ensure that everyone understands what has changed, why it has changed, and how to adapt to the new version.\n\nWe can get the changes from the PR diff and summarize them in a clear and concise manner. Aim to provide a clear APIs changes for users to follow.\n\n### Format\n\nWe pefer the following format for breaking changes descriptions:\n\n1. Use bullet list for each breaking change item.\n2. Each item should have title and a code block showing the old and new usage by use `diff`.\n3. Use `## Breaking Changes` as the section title.\n4. Use english language.\n\n**For example:**\n\n````md\n## Breaking Changes\n\n- Added `id` parameter to `Sidebar::new`.\n\n```diff\n- Sidebar::new()\n+ Sidebar::new(\"sidebar\")\n```\n\n- Removed the `left` and `right` methods; use `side` instead.\n  > Default is left.\n\n```diff\n- Sidebar::right()\n+ Sidebar::new(\"sidebar\").side(Side::Right)\n```\n````\n"
  },
  {
    "path": ".claude/skills/gpui-action/SKILL.md",
    "content": "---\nname: gpui-action\ndescription: Action definitions and keyboard shortcuts in GPUI. Use when implementing actions, keyboard shortcuts, or key bindings.\n---\n\n## Overview\n\nActions provide declarative keyboard-driven UI interactions in GPUI.\n\n**Key Concepts:**\n- Define actions with `actions!` macro or `#[derive(Action)]`\n- Bind keys with `cx.bind_keys()`\n- Handle with `.on_action()` on elements\n- Context-aware via `key_context()`\n\n## Quick Start\n\n### Simple Actions\n\n```rust\nuse gpui::actions;\n\nactions!(editor, [MoveUp, MoveDown, Save, Quit]);\n\nconst CONTEXT: &str = \"Editor\";\n\npub fn init(cx: &mut App) {\n    cx.bind_keys([\n        KeyBinding::new(\"up\", MoveUp, Some(CONTEXT)),\n        KeyBinding::new(\"down\", MoveDown, Some(CONTEXT)),\n        KeyBinding::new(\"cmd-s\", Save, Some(CONTEXT)),\n        KeyBinding::new(\"cmd-q\", Quit, Some(CONTEXT)),\n    ]);\n}\n\nimpl Render for Editor {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .key_context(CONTEXT)\n            .on_action(cx.listener(Self::move_up))\n            .on_action(cx.listener(Self::move_down))\n            .on_action(cx.listener(Self::save))\n    }\n}\n\nimpl Editor {\n    fn move_up(&mut self, _: &MoveUp, cx: &mut Context<Self>) {\n        // Handle move up\n        cx.notify();\n    }\n\n    fn move_down(&mut self, _: &MoveDown, cx: &mut Context<Self>) {\n        cx.notify();\n    }\n\n    fn save(&mut self, _: &Save, cx: &mut Context<Self>) {\n        // Save logic\n        cx.notify();\n    }\n}\n```\n\n### Actions with Parameters\n\n```rust\n#[derive(Clone, PartialEq, Action, Deserialize)]\n#[action(namespace = editor)]\npub struct InsertText {\n    pub text: String,\n}\n\n#[derive(Action, Clone, PartialEq, Eq, Deserialize)]\n#[action(namespace = editor, no_json)]\npub struct Digit(pub u8);\n\ncx.bind_keys([\n    KeyBinding::new(\"0\", Digit(0), Some(CONTEXT)),\n    KeyBinding::new(\"1\", Digit(1), Some(CONTEXT)),\n    // ...\n]);\n\nimpl Editor {\n    fn on_digit(&mut self, action: &Digit, cx: &mut Context<Self>) {\n        self.insert_digit(action.0, cx);\n    }\n}\n```\n\n## Key Formats\n\n```rust\n// Modifiers\n\"cmd-s\"         // Command (macOS) / Ctrl (Windows/Linux)\n\"ctrl-c\"        // Control\n\"alt-f\"         // Alt\n\"shift-tab\"     // Shift\n\"cmd-ctrl-f\"    // Multiple modifiers\n\n// Keys\n\"a-z\", \"0-9\"    // Letters and numbers\n\"f1-f12\"        // Function keys\n\"up\", \"down\", \"left\", \"right\"\n\"enter\", \"escape\", \"space\", \"tab\"\n\"backspace\", \"delete\"\n\"-\", \"=\", \"[\", \"]\", etc.  // Special characters\n```\n\n## Action Naming\n\nPrefer verb-noun pattern:\n\n```rust\nactions!([\n    OpenFile,      // ✅ Good\n    CloseWindow,   // ✅ Good\n    ToggleSidebar, // ✅ Good\n    Save,          // ✅ Good (common exception)\n]);\n```\n\n## Context-Aware Bindings\n\n```rust\nconst EDITOR_CONTEXT: &str = \"Editor\";\nconst MODAL_CONTEXT: &str = \"Modal\";\n\n// Same key, different contexts\ncx.bind_keys([\n    KeyBinding::new(\"escape\", CloseModal, Some(MODAL_CONTEXT)),\n    KeyBinding::new(\"escape\", ClearSelection, Some(EDITOR_CONTEXT)),\n]);\n\n// Set context on element\ndiv()\n    .key_context(EDITOR_CONTEXT)\n    .child(editor_content)\n```\n\n## Best Practices\n\n### ✅ Use Contexts\n\n```rust\n// ✅ Good: Context-aware\ndiv()\n    .key_context(\"MyComponent\")\n    .on_action(cx.listener(Self::handle))\n```\n\n### ✅ Name Actions Clearly\n\n```rust\n// ✅ Good: Clear intent\nactions!([\n    SaveDocument,\n    CloseTab,\n    TogglePreview,\n]);\n```\n\n### ✅ Handle with Listeners\n\n```rust\n// ✅ Good: Proper handler naming\nimpl MyComponent {\n    fn on_action_save(&mut self, _: &Save, cx: &mut Context<Self>) {\n        // Handle save\n        cx.notify();\n    }\n}\n\ndiv().on_action(cx.listener(Self::on_action_save))\n```\n\n## Reference Documentation\n\n- **Complete Guide**: See [reference.md](references/reference.md)\n  - Action definition, keybinding, dispatch\n  - Focus-based routing, best practices\n  - Performance, accessibility\n"
  },
  {
    "path": ".claude/skills/gpui-async/SKILL.md",
    "content": "---\nname: gpui-async\ndescription: Async operations and background tasks in GPUI. Use when working with async, spawn, background tasks, or concurrent operations. Essential for handling async I/O, long-running computations, and coordinating between foreground UI updates and background work.\n---\n\n## Overview\n\nGPUI provides integrated async runtime for foreground UI updates and background computation.\n\n**Key Concepts:**\n\n- **Foreground tasks**: UI thread, can update entities (`cx.spawn`)\n- **Background tasks**: Worker threads, CPU-intensive work (`cx.background_spawn`)\n- All entity updates happen on foreground thread\n\n## Quick Start\n\n### Foreground Tasks (UI Updates)\n\n```rust\nimpl MyComponent {\n    fn fetch_data(&mut self, cx: &mut Context<Self>) {\n        let entity = cx.entity().downgrade();\n\n        cx.spawn(async move |cx| {\n            // Runs on UI thread, can await and update entities\n            let data = fetch_from_api().await;\n\n            entity.update(cx, |state, cx| {\n                state.data = Some(data);\n                cx.notify();\n            }).ok();\n        }).detach();\n    }\n}\n```\n\n### Background Tasks (Heavy Work)\n\n```rust\nimpl MyComponent {\n    fn process_file(&mut self, cx: &mut Context<Self>) {\n        let entity = cx.entity().downgrade();\n\n        cx.background_spawn(async move {\n            // Runs on background thread, CPU-intensive\n            let result = heavy_computation().await;\n            result\n        })\n        .then(cx.spawn(move |result, cx| {\n            // Back to foreground to update UI\n            entity.update(cx, |state, cx| {\n                state.result = result;\n                cx.notify();\n            }).ok();\n        }))\n        .detach();\n    }\n}\n```\n\n### Task Management\n\n```rust\nstruct MyView {\n    _task: Task<()>,  // Prefix with _ if stored but not accessed\n}\n\nimpl MyView {\n    fn new(cx: &mut Context<Self>) -> Self {\n        let entity = cx.entity().downgrade();\n\n        let _task = cx.spawn(async move |cx| {\n            // Task automatically cancelled when dropped\n            loop {\n                tokio::time::sleep(Duration::from_secs(1)).await;\n                entity.update(cx, |state, cx| {\n                    state.tick();\n                    cx.notify();\n                }).ok();\n            }\n        });\n\n        Self { _task }\n    }\n}\n```\n\n## Core Patterns\n\n### 1. Async Data Fetching\n\n```rust\ncx.spawn(async move |cx| {\n    let data = fetch_data().await?;\n    entity.update(cx, |state, cx| {\n        state.data = Some(data);\n        cx.notify();\n    })?;\n    Ok::<_, anyhow::Error>(())\n}).detach();\n```\n\n### 2. Background Computation + UI Update\n\n```rust\ncx.background_spawn(async move {\n    heavy_work()\n})\n.then(cx.spawn(move |result, cx| {\n    entity.update(cx, |state, cx| {\n        state.result = result;\n        cx.notify();\n    }).ok();\n}))\n.detach();\n```\n\n### 3. Periodic Tasks\n\n```rust\ncx.spawn(async move |cx| {\n    loop {\n        tokio::time::sleep(Duration::from_secs(5)).await;\n        // Update every 5 seconds\n    }\n}).detach();\n```\n\n### 4. Task Cancellation\n\nTasks are automatically cancelled when dropped. Store in struct to keep alive.\n\n## Common Pitfalls\n\n### ❌ Don't: Update entities from background tasks\n\n```rust\n// ❌ Wrong: Can't update entities from background thread\ncx.background_spawn(async move {\n    entity.update(cx, |state, cx| { // Compile error!\n        state.data = data;\n    });\n});\n```\n\n### ✅ Do: Use foreground task or chain\n\n```rust\n// ✅ Correct: Chain with foreground task\ncx.background_spawn(async move { data })\n    .then(cx.spawn(move |data, cx| {\n        entity.update(cx, |state, cx| {\n            state.data = data;\n            cx.notify();\n        }).ok();\n    }))\n    .detach();\n```\n\n## Reference Documentation\n\n### Complete Guides\n\n- **API Reference**: See [api-reference.md](references/api-reference.md)\n  - Task types, spawning methods, contexts\n  - Executors, cancellation, error handling\n\n- **Patterns**: See [patterns.md](references/patterns.md)\n  - Data fetching, background processing\n  - Polling, debouncing, parallel tasks\n  - Pattern selection guide\n\n- **Best Practices**: See [best-practices.md](references/best-practices.md)\n  - Error handling, cancellation\n  - Performance optimization, testing\n  - Common pitfalls and solutions\n"
  },
  {
    "path": ".claude/skills/gpui-context/SKILL.md",
    "content": "---\nname: gpui-context\ndescription: Context management in GPUI including App, Window, and AsyncApp. Use when working with contexts, entity updates, or window operations. Different context types provide different capabilities for UI rendering, entity management, and async operations.\n---\n\n## Overview\n\nGPUI uses different context types for different scenarios:\n\n**Context Types:**\n- **`App`**: Global app state, entity creation\n- **`Window`**: Window-specific operations, painting, layout\n- **`Context<T>`**: Entity-specific context for component `T`\n- **`AsyncApp`**: Async context for foreground tasks\n- **`AsyncWindowContext`**: Async context with window access\n\n## Quick Start\n\n### Context<T> - Component Context\n\n```rust\nimpl MyComponent {\n    fn update_state(&mut self, cx: &mut Context<Self>) {\n        self.value = 42;\n        cx.notify(); // Trigger re-render\n\n        // Spawn async task\n        cx.spawn(async move |cx| {\n            // Async work\n        }).detach();\n\n        // Get current entity\n        let entity = cx.entity();\n    }\n}\n```\n\n### App - Global Context\n\n```rust\nfn main() {\n    let app = Application::new();\n    app.run(|cx: &mut App| {\n        // Create entities\n        let entity = cx.new(|cx| MyState::default());\n\n        // Open windows\n        cx.open_window(WindowOptions::default(), |window, cx| {\n            cx.new(|cx| Root::new(view, window, cx))\n        });\n    });\n}\n```\n\n### Window - Window Context\n\n```rust\nimpl Render for MyView {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        // Window operations\n        let is_focused = window.is_window_focused();\n        let bounds = window.bounds();\n\n        div().child(\"Content\")\n    }\n}\n```\n\n### AsyncApp - Async Context\n\n```rust\ncx.spawn(async move |cx: &mut AsyncApp| {\n    let data = fetch_data().await;\n\n    entity.update(cx, |state, inner_cx| {\n        state.data = data;\n        inner_cx.notify();\n    }).ok();\n}).detach();\n```\n\n## Common Operations\n\n### Entity Operations\n\n```rust\n// Create entity\nlet entity = cx.new(|cx| MyState::default());\n\n// Update entity\nentity.update(cx, |state, cx| {\n    state.value = 42;\n    cx.notify();\n});\n\n// Read entity\nlet value = entity.read(cx).value;\n```\n\n### Notifications and Events\n\n```rust\n// Trigger re-render\ncx.notify();\n\n// Emit event\ncx.emit(MyEvent::Updated);\n\n// Observe entity\ncx.observe(&entity, |this, observed, cx| {\n    // React to changes\n}).detach();\n\n// Subscribe to events\ncx.subscribe(&entity, |this, source, event, cx| {\n    // Handle event\n}).detach();\n```\n\n### Window Operations\n\n```rust\n// Window state\nlet focused = window.is_window_focused();\nlet bounds = window.bounds();\nlet scale = window.scale_factor();\n\n// Close window\nwindow.remove_window();\n```\n\n### Async Operations\n\n```rust\n// Spawn foreground task\ncx.spawn(async move |cx| {\n    // Async work with entity access\n}).detach();\n\n// Spawn background task\ncx.background_spawn(async move {\n    // Heavy computation\n}).detach();\n```\n\n## Context Hierarchy\n\n```\nApp (Global)\n  └─ Window (Per-window)\n       └─ Context<T> (Per-component)\n            └─ AsyncApp (In async tasks)\n                 └─ AsyncWindowContext (Async + Window)\n```\n\n## Reference Documentation\n\n- **API Reference**: See [api-reference.md](references/api-reference.md)\n  - Complete context API, methods, conversions\n  - Entity operations, window operations\n  - Async contexts, best practices\n"
  },
  {
    "path": ".claude/skills/gpui-element/SKILL.md",
    "content": "---\nname: gpui-element\ndescription: Implementing custom elements using GPUI's low-level Element API (vs. high-level Render/RenderOnce APIs). Use when you need maximum control over layout, prepaint, and paint phases for complex, performance-critical custom UI components that cannot be achieved with Render/RenderOnce traits.\n---\n\n## When to Use\n\nUse the low-level `Element` trait when:\n- Need fine-grained control over layout calculation\n- Building complex, performance-critical components\n- Implementing custom layout algorithms (masonry, circular, etc.)\n- High-level `Render`/`RenderOnce` APIs are insufficient\n\n**Prefer `Render`/`RenderOnce` for:** Simple components, standard layouts, declarative UI\n\n## Quick Start\n\nThe `Element` trait provides direct control over three rendering phases:\n\n```rust\nimpl Element for MyElement {\n    type RequestLayoutState = MyLayoutState;  // Data passed to later phases\n    type PrepaintState = MyPaintState;        // Data for painting\n\n    fn id(&self) -> Option<ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    // Phase 1: Calculate sizes and positions\n    fn request_layout(&mut self, .., window: &mut Window, cx: &mut App)\n        -> (LayoutId, Self::RequestLayoutState)\n    {\n        let layout_id = window.request_layout(\n            Style { size: size(px(200.), px(100.)), ..default() },\n            vec![],\n            cx\n        );\n        (layout_id, MyLayoutState { /* ... */ })\n    }\n\n    // Phase 2: Create hitboxes, prepare for painting\n    fn prepaint(&mut self, .., bounds: Bounds<Pixels>, layout: &mut Self::RequestLayoutState,\n                window: &mut Window, cx: &mut App) -> Self::PrepaintState\n    {\n        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);\n        MyPaintState { hitbox }\n    }\n\n    // Phase 3: Render and handle interactions\n    fn paint(&mut self, .., bounds: Bounds<Pixels>, layout: &mut Self::RequestLayoutState,\n             paint_state: &mut Self::PrepaintState, window: &mut Window, cx: &mut App)\n    {\n        window.paint_quad(paint_quad(bounds, Corners::all(px(4.)), cx.theme().background));\n\n        window.on_mouse_event({\n            let hitbox = paint_state.hitbox.clone();\n            move |event: &MouseDownEvent, phase, window, cx| {\n                if hitbox.is_hovered(window) && phase.bubble() {\n                    // Handle interaction\n                    cx.stop_propagation();\n                }\n            }\n        });\n    }\n}\n\n// Enable element to be used as child\nimpl IntoElement for MyElement {\n    type Element = Self;\n    fn into_element(self) -> Self::Element { self }\n}\n```\n\n## Core Concepts\n\n### Three-Phase Rendering\n\n1. **request_layout**: Calculate sizes and positions, return layout ID and state\n2. **prepaint**: Create hitboxes, compute final bounds, prepare for painting\n3. **paint**: Render element, set up interactions (mouse events, cursor styles)\n\n### State Flow\n\n```\nRequestLayoutState → PrepaintState → paint\n```\n\nState flows in one direction through associated types, passed as mutable references between phases.\n\n### Key Operations\n\n- **Layout**: `window.request_layout(style, children, cx)` - Create layout node\n- **Hitboxes**: `window.insert_hitbox(bounds, behavior)` - Create interaction area\n- **Painting**: `window.paint_quad(...)` - Render visual content\n- **Events**: `window.on_mouse_event(handler)` - Handle user input\n\n## Reference Documentation\n\n### Complete API Documentation\n- **Element Trait API**: See [api-reference.md](references/api-reference.md)\n  - Associated types, methods, parameters, return values\n  - Hitbox system, event handling, cursor styles\n\n### Implementation Guides\n- **Examples**: See [examples.md](references/examples.md)\n  - Simple text element with highlighting\n  - Interactive element with selection\n  - Complex element with child management\n\n- **Best Practices**: See [best-practices.md](references/best-practices.md)\n  - State management, performance optimization\n  - Interaction handling, layout strategies\n  - Error handling, testing, common pitfalls\n\n- **Common Patterns**: See [patterns.md](references/patterns.md)\n  - Text rendering, container, interactive, composite, scrollable patterns\n  - Pattern selection guide\n\n- **Advanced Patterns**: See [advanced-patterns.md](references/advanced-patterns.md)\n  - Custom layout algorithms (masonry, circular)\n  - Element composition with traits\n  - Async updates, memoization, virtual lists\n"
  },
  {
    "path": ".claude/skills/gpui-element/references/advanced-patterns.md",
    "content": "# Advanced Element Patterns\n\nAdvanced techniques and patterns for implementing sophisticated GPUI elements.\n\n## Custom Layout Algorithms\n\nImplementing custom layout algorithms not supported by GPUI's built-in layouts.\n\n### Masonry Layout (Pinterest-Style)\n\n```rust\npub struct MasonryLayout {\n    id: ElementId,\n    columns: usize,\n    gap: Pixels,\n    children: Vec<AnyElement>,\n}\n\nstruct MasonryLayoutState {\n    column_layouts: Vec<Vec<LayoutId>>,\n    column_heights: Vec<Pixels>,\n}\n\nstruct MasonryPaintState {\n    child_bounds: Vec<Bounds<Pixels>>,\n}\n\nimpl Element for MasonryLayout {\n    type RequestLayoutState = MasonryLayoutState;\n    type PrepaintState = MasonryPaintState;\n\n    fn id(&self) -> Option<ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App\n    ) -> (LayoutId, MasonryLayoutState) {\n        // Initialize columns\n        let mut columns: Vec<Vec<LayoutId>> = vec![Vec::new(); self.columns];\n        let mut column_heights = vec![px(0.); self.columns];\n\n        // Distribute children across columns\n        for child in &mut self.children {\n            let (child_layout_id, _) = child.request_layout(\n                global_id,\n                inspector_id,\n                window,\n                cx\n            );\n\n            let child_size = window.layout_bounds(child_layout_id).size;\n\n            // Find shortest column\n            let min_column_idx = column_heights\n                .iter()\n                .enumerate()\n                .min_by(|a, b| a.1.partial_cmp(b.1).unwrap())\n                .unwrap()\n                .0;\n\n            // Add child to shortest column\n            columns[min_column_idx].push(child_layout_id);\n            column_heights[min_column_idx] += child_size.height + self.gap;\n        }\n\n        // Calculate total layout size\n        let column_width = px(200.); // Fixed column width\n        let total_width = column_width * self.columns as f32\n            + self.gap * (self.columns - 1) as f32;\n        let total_height = column_heights.iter()\n            .max_by(|a, b| a.partial_cmp(b).unwrap())\n            .copied()\n            .unwrap_or(px(0.));\n\n        let layout_id = window.request_layout(\n            Style {\n                size: size(total_width, total_height),\n                ..default()\n            },\n            columns.iter().flatten().copied().collect(),\n            cx\n        );\n\n        (layout_id, MasonryLayoutState {\n            column_layouts: columns,\n            column_heights,\n        })\n    }\n\n    fn prepaint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        layout_state: &mut MasonryLayoutState,\n        window: &mut Window,\n        cx: &mut App\n    ) -> MasonryPaintState {\n        let column_width = px(200.);\n        let mut child_bounds = Vec::new();\n\n        // Position children in columns\n        for (col_idx, column) in layout_state.column_layouts.iter().enumerate() {\n            let x_offset = bounds.left()\n                + (column_width + self.gap) * col_idx as f32;\n            let mut y_offset = bounds.top();\n\n            for (child_idx, layout_id) in column.iter().enumerate() {\n                let child_size = window.layout_bounds(*layout_id).size;\n                let child_bound = Bounds::new(\n                    point(x_offset, y_offset),\n                    size(column_width, child_size.height)\n                );\n\n                self.children[child_idx].prepaint(\n                    global_id,\n                    inspector_id,\n                    child_bound,\n                    window,\n                    cx\n                );\n\n                child_bounds.push(child_bound);\n                y_offset += child_size.height + self.gap;\n            }\n        }\n\n        MasonryPaintState { child_bounds }\n    }\n\n    fn paint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        _bounds: Bounds<Pixels>,\n        _layout_state: &mut MasonryLayoutState,\n        paint_state: &mut MasonryPaintState,\n        window: &mut Window,\n        cx: &mut App\n    ) {\n        for (child, bounds) in self.children.iter_mut().zip(&paint_state.child_bounds) {\n            child.paint(global_id, inspector_id, *bounds, window, cx);\n        }\n    }\n}\n```\n\n### Circular Layout\n\n```rust\npub struct CircularLayout {\n    id: ElementId,\n    radius: Pixels,\n    children: Vec<AnyElement>,\n}\n\nimpl Element for CircularLayout {\n    type RequestLayoutState = Vec<LayoutId>;\n    type PrepaintState = Vec<Bounds<Pixels>>;\n\n    fn request_layout(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App\n    ) -> (LayoutId, Vec<LayoutId>) {\n        let child_layouts: Vec<_> = self.children\n            .iter_mut()\n            .map(|child| child.request_layout(global_id, inspector_id, window, cx).0)\n            .collect();\n\n        let diameter = self.radius * 2.;\n        let layout_id = window.request_layout(\n            Style {\n                size: size(diameter, diameter),\n                ..default()\n            },\n            child_layouts.clone(),\n            cx\n        );\n\n        (layout_id, child_layouts)\n    }\n\n    fn prepaint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        layout_ids: &mut Vec<LayoutId>,\n        window: &mut Window,\n        cx: &mut App\n    ) -> Vec<Bounds<Pixels>> {\n        let center = bounds.center();\n        let angle_step = 2.0 * std::f32::consts::PI / self.children.len() as f32;\n\n        let mut child_bounds = Vec::new();\n\n        for (i, (child, layout_id)) in self.children.iter_mut()\n            .zip(layout_ids.iter())\n            .enumerate()\n        {\n            let angle = angle_step * i as f32;\n            let child_size = window.layout_bounds(*layout_id).size;\n\n            // Position child on circle\n            let x = center.x + self.radius * angle.cos() - child_size.width / 2.;\n            let y = center.y + self.radius * angle.sin() - child_size.height / 2.;\n\n            let child_bound = Bounds::new(point(x, y), child_size);\n\n            child.prepaint(global_id, inspector_id, child_bound, window, cx);\n            child_bounds.push(child_bound);\n        }\n\n        child_bounds\n    }\n\n    fn paint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        _bounds: Bounds<Pixels>,\n        _layout_ids: &mut Vec<LayoutId>,\n        child_bounds: &mut Vec<Bounds<Pixels>>,\n        window: &mut Window,\n        cx: &mut App\n    ) {\n        for (child, bounds) in self.children.iter_mut().zip(child_bounds) {\n            child.paint(global_id, inspector_id, *bounds, window, cx);\n        }\n    }\n}\n```\n\n## Element Composition with Traits\n\nCreate reusable behaviors via traits for element composition.\n\n### Hoverable Trait\n\n```rust\npub trait Hoverable: Element {\n    fn on_hover<F>(&mut self, f: F) -> &mut Self\n    where\n        F: Fn(&mut Window, &mut App) + 'static;\n\n    fn on_hover_end<F>(&mut self, f: F) -> &mut Self\n    where\n        F: Fn(&mut Window, &mut App) + 'static;\n}\n\n// Implementation for custom element\npub struct HoverableElement {\n    id: ElementId,\n    content: AnyElement,\n    hover_handlers: Vec<Box<dyn Fn(&mut Window, &mut App)>>,\n    hover_end_handlers: Vec<Box<dyn Fn(&mut Window, &mut App)>>,\n    was_hovered: bool,\n}\n\nimpl Hoverable for HoverableElement {\n    fn on_hover<F>(&mut self, f: F) -> &mut Self\n    where\n        F: Fn(&mut Window, &mut App) + 'static\n    {\n        self.hover_handlers.push(Box::new(f));\n        self\n    }\n\n    fn on_hover_end<F>(&mut self, f: F) -> &mut Self\n    where\n        F: Fn(&mut Window, &mut App) + 'static\n    {\n        self.hover_end_handlers.push(Box::new(f));\n        self\n    }\n}\n\nimpl Element for HoverableElement {\n    type RequestLayoutState = LayoutId;\n    type PrepaintState = Hitbox;\n\n    fn paint(\n        &mut self,\n        _global_id: Option<&GlobalElementId>,\n        _inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        _layout: &mut LayoutId,\n        hitbox: &mut Hitbox,\n        window: &mut Window,\n        cx: &mut App\n    ) {\n        let is_hovered = hitbox.is_hovered(window);\n\n        // Trigger hover events\n        if is_hovered && !self.was_hovered {\n            for handler in &self.hover_handlers {\n                handler(window, cx);\n            }\n        } else if !is_hovered && self.was_hovered {\n            for handler in &self.hover_end_handlers {\n                handler(window, cx);\n            }\n        }\n\n        self.was_hovered = is_hovered;\n\n        // Paint content\n        self.content.paint(bounds, window, cx);\n    }\n\n    // ... other methods\n}\n```\n\n### Clickable Trait\n\n```rust\npub trait Clickable: Element {\n    fn on_click<F>(&mut self, f: F) -> &mut Self\n    where\n        F: Fn(&MouseUpEvent, &mut Window, &mut App) + 'static;\n\n    fn on_double_click<F>(&mut self, f: F) -> &mut Self\n    where\n        F: Fn(&MouseUpEvent, &mut Window, &mut App) + 'static;\n}\n\npub struct ClickableElement {\n    id: ElementId,\n    content: AnyElement,\n    click_handlers: Vec<Box<dyn Fn(&MouseUpEvent, &mut Window, &mut App)>>,\n    double_click_handlers: Vec<Box<dyn Fn(&MouseUpEvent, &mut Window, &mut App)>>,\n    last_click_time: Option<Instant>,\n}\n\nimpl Clickable for ClickableElement {\n    fn on_click<F>(&mut self, f: F) -> &mut Self\n    where\n        F: Fn(&MouseUpEvent, &mut Window, &mut App) + 'static\n    {\n        self.click_handlers.push(Box::new(f));\n        self\n    }\n\n    fn on_double_click<F>(&mut self, f: F) -> &mut Self\n    where\n        F: Fn(&MouseUpEvent, &mut Window, &mut App) + 'static\n    {\n        self.double_click_handlers.push(Box::new(f));\n        self\n    }\n}\n```\n\n## Async Element Updates\n\nElements that update based on async operations.\n\n```rust\npub struct AsyncElement {\n    id: ElementId,\n    state: Entity<AsyncState>,\n    loading: bool,\n    data: Option<String>,\n}\n\npub struct AsyncState {\n    loading: bool,\n    data: Option<String>,\n}\n\nimpl Element for AsyncElement {\n    type RequestLayoutState = ();\n    type PrepaintState = Hitbox;\n\n    fn paint(\n        &mut self,\n        _global_id: Option<&GlobalElementId>,\n        _inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        _layout: &mut (),\n        hitbox: &mut Hitbox,\n        window: &mut Window,\n        cx: &mut App\n    ) {\n        // Display loading or data\n        if self.loading {\n            // Paint loading indicator\n            self.paint_loading(bounds, window, cx);\n        } else if let Some(data) = &self.data {\n            // Paint data\n            self.paint_data(data, bounds, window, cx);\n        }\n\n        // Trigger async update on click\n        window.on_mouse_event({\n            let state = self.state.clone();\n            let hitbox = hitbox.clone();\n\n            move |event: &MouseUpEvent, phase, window, cx| {\n                if hitbox.is_hovered(window) && phase.bubble() {\n                    // Spawn async task\n                    cx.spawn({\n                        let state = state.clone();\n                        async move {\n                            // Perform async operation\n                            let result = fetch_data_async().await;\n\n                            // Update state on completion\n                            state.update(cx, |state, cx| {\n                                state.loading = false;\n                                state.data = Some(result);\n                                cx.notify();\n                            });\n                        }\n                    }).detach();\n\n                    // Set loading state immediately\n                    state.update(cx, |state, cx| {\n                        state.loading = true;\n                        cx.notify();\n                    });\n\n                    cx.stop_propagation();\n                }\n            }\n        });\n    }\n\n    // ... other methods\n}\n\nasync fn fetch_data_async() -> String {\n    // Simulate async operation\n    tokio::time::sleep(Duration::from_secs(1)).await;\n    \"Data loaded!\".to_string()\n}\n```\n\n## Element Memoization\n\nOptimize performance by memoizing expensive element computations.\n\n```rust\npub struct MemoizedElement<T: PartialEq + Clone + 'static> {\n    id: ElementId,\n    value: T,\n    render_fn: Box<dyn Fn(&T) -> AnyElement>,\n    cached_element: Option<AnyElement>,\n    last_value: Option<T>,\n}\n\nimpl<T: PartialEq + Clone + 'static> MemoizedElement<T> {\n    pub fn new<F>(id: ElementId, value: T, render_fn: F) -> Self\n    where\n        F: Fn(&T) -> AnyElement + 'static,\n    {\n        Self {\n            id,\n            value,\n            render_fn: Box::new(render_fn),\n            cached_element: None,\n            last_value: None,\n        }\n    }\n}\n\nimpl<T: PartialEq + Clone + 'static> Element for MemoizedElement<T> {\n    type RequestLayoutState = LayoutId;\n    type PrepaintState = ();\n\n    fn id(&self) -> Option<ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App\n    ) -> (LayoutId, LayoutId) {\n        // Check if value changed\n        if self.last_value.as_ref() != Some(&self.value) || self.cached_element.is_none() {\n            // Recompute element\n            self.cached_element = Some((self.render_fn)(&self.value));\n            self.last_value = Some(self.value.clone());\n        }\n\n        // Request layout for cached element\n        let (layout_id, _) = self.cached_element\n            .as_mut()\n            .unwrap()\n            .request_layout(global_id, inspector_id, window, cx);\n\n        (layout_id, layout_id)\n    }\n\n    fn prepaint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        _layout_id: &mut LayoutId,\n        window: &mut Window,\n        cx: &mut App\n    ) -> () {\n        self.cached_element\n            .as_mut()\n            .unwrap()\n            .prepaint(global_id, inspector_id, bounds, window, cx);\n    }\n\n    fn paint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        _layout_id: &mut LayoutId,\n        _: &mut (),\n        window: &mut Window,\n        cx: &mut App\n    ) {\n        self.cached_element\n            .as_mut()\n            .unwrap()\n            .paint(global_id, inspector_id, bounds, window, cx);\n    }\n}\n\n// Usage\nfn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n    MemoizedElement::new(\n        ElementId::Name(\"memoized\".into()),\n        self.expensive_value.clone(),\n        |value| {\n            // Expensive rendering function only called when value changes\n            div().child(format!(\"Computed: {}\", value))\n        }\n    )\n}\n```\n\n## Virtual List Pattern\n\nEfficiently render large lists by only rendering visible items.\n\n```rust\npub struct VirtualList {\n    id: ElementId,\n    item_count: usize,\n    item_height: Pixels,\n    viewport_height: Pixels,\n    scroll_offset: Pixels,\n    render_item: Box<dyn Fn(usize) -> AnyElement>,\n}\n\nstruct VirtualListState {\n    visible_range: Range<usize>,\n    visible_item_layouts: Vec<LayoutId>,\n}\n\nimpl Element for VirtualList {\n    type RequestLayoutState = VirtualListState;\n    type PrepaintState = Hitbox;\n\n    fn request_layout(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App\n    ) -> (LayoutId, VirtualListState) {\n        // Calculate visible range\n        let start_idx = (self.scroll_offset / self.item_height).floor() as usize;\n        let end_idx = ((self.scroll_offset + self.viewport_height) / self.item_height)\n            .ceil() as usize;\n        let visible_range = start_idx..end_idx.min(self.item_count);\n\n        // Request layout only for visible items\n        let visible_item_layouts: Vec<_> = visible_range.clone()\n            .map(|i| {\n                let mut item = (self.render_item)(i);\n                item.request_layout(global_id, inspector_id, window, cx).0\n            })\n            .collect();\n\n        let total_height = self.item_height * self.item_count as f32;\n        let layout_id = window.request_layout(\n            Style {\n                size: size(relative(1.0), self.viewport_height),\n                overflow: Overflow::Hidden,\n                ..default()\n            },\n            visible_item_layouts.clone(),\n            cx\n        );\n\n        (layout_id, VirtualListState {\n            visible_range,\n            visible_item_layouts,\n        })\n    }\n\n    fn prepaint(\n        &mut self,\n        _global_id: Option<&GlobalElementId>,\n        _inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        state: &mut VirtualListState,\n        window: &mut Window,\n        _cx: &mut App\n    ) -> Hitbox {\n        // Prepaint visible items at correct positions\n        for (i, layout_id) in state.visible_item_layouts.iter().enumerate() {\n            let item_idx = state.visible_range.start + i;\n            let y = item_idx as f32 * self.item_height - self.scroll_offset;\n            let item_bounds = Bounds::new(\n                point(bounds.left(), bounds.top() + y),\n                size(bounds.width(), self.item_height)\n            );\n\n            // Prepaint if visible\n            if item_bounds.intersects(&bounds) {\n                // Prepaint item...\n            }\n        }\n\n        window.insert_hitbox(bounds, HitboxBehavior::Normal)\n    }\n\n    fn paint(\n        &mut self,\n        _global_id: Option<&GlobalElementId>,\n        _inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        state: &mut VirtualListState,\n        hitbox: &mut Hitbox,\n        window: &mut Window,\n        cx: &mut App\n    ) {\n        // Paint visible items\n        for (i, _layout_id) in state.visible_item_layouts.iter().enumerate() {\n            let item_idx = state.visible_range.start + i;\n            let y = item_idx as f32 * self.item_height - self.scroll_offset;\n            let item_bounds = Bounds::new(\n                point(bounds.left(), bounds.top() + y),\n                size(bounds.width(), self.item_height)\n            );\n\n            if item_bounds.intersects(&bounds) {\n                let mut item = (self.render_item)(item_idx);\n                item.paint(item_bounds, window, cx);\n            }\n        }\n\n        // Handle scroll\n        window.on_mouse_event({\n            let hitbox = hitbox.clone();\n            let total_height = self.item_height * self.item_count as f32;\n\n            move |event: &ScrollWheelEvent, phase, window, cx| {\n                if hitbox.is_hovered(window) && phase.bubble() {\n                    self.scroll_offset -= event.delta.y;\n                    self.scroll_offset = self.scroll_offset\n                        .max(px(0.))\n                        .min(total_height - self.viewport_height);\n                    cx.notify();\n                    cx.stop_propagation();\n                }\n            }\n        });\n    }\n}\n\n// Usage: Efficiently render 10,000 items\nlet virtual_list = VirtualList {\n    id: ElementId::Name(\"large-list\".into()),\n    item_count: 10_000,\n    item_height: px(40.),\n    viewport_height: px(400.),\n    scroll_offset: px(0.),\n    render_item: Box::new(|index| {\n        div().child(format!(\"Item {}\", index))\n    }),\n};\n```\n\nThese advanced patterns enable sophisticated element implementations while maintaining performance and code quality.\n"
  },
  {
    "path": ".claude/skills/gpui-element/references/api-reference.md",
    "content": "# Element API Reference\n\nComplete API documentation for GPUI's low-level Element trait.\n\n## Element Trait Structure\n\nThe `Element` trait requires implementing three associated types and five methods:\n\n```rust\npub trait Element: 'static + IntoElement {\n    type RequestLayoutState: 'static;\n    type PrepaintState: 'static;\n\n    fn id(&self) -> Option<ElementId>;\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>>;\n    fn request_layout(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (LayoutId, Self::RequestLayoutState);\n    fn prepaint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        request_layout: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::PrepaintState;\n    fn paint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        request_layout: &mut Self::RequestLayoutState,\n        prepaint: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App,\n    );\n}\n```\n\n## Associated Types\n\n### RequestLayoutState\n\nData passed from `request_layout` to `prepaint` and `paint` phases.\n\n**Usage:**\n- Store layout calculations (styled text, child layout IDs)\n- Cache expensive computations\n- Pass child state between phases\n\n**Examples:**\n```rust\n// Simple: no state needed\ntype RequestLayoutState = ();\n\n// Single value\ntype RequestLayoutState = StyledText;\n\n// Multiple values\ntype RequestLayoutState = (StyledText, Vec<ChildLayout>);\n\n// Complex struct\npub struct MyLayoutState {\n    pub styled_text: StyledText,\n    pub child_layouts: Vec<(LayoutId, ChildState)>,\n    pub computed_bounds: Bounds<Pixels>,\n}\ntype RequestLayoutState = MyLayoutState;\n```\n\n### PrepaintState\n\nData passed from `prepaint` to `paint` phase.\n\n**Usage:**\n- Store hitboxes for interaction\n- Cache visual bounds\n- Store prepaint results\n\n**Examples:**\n```rust\n// Simple: just a hitbox\ntype PrepaintState = Hitbox;\n\n// Optional hitbox\ntype PrepaintState = Option<Hitbox>;\n\n// Multiple values\ntype PrepaintState = (Hitbox, Vec<Bounds<Pixels>>);\n\n// Complex struct\npub struct MyPaintState {\n    pub hitbox: Hitbox,\n    pub child_bounds: Vec<Bounds<Pixels>>,\n    pub visible_range: Range<usize>,\n}\ntype PrepaintState = MyPaintState;\n```\n\n## Methods\n\n### id()\n\nReturns optional unique identifier for debugging and inspection.\n\n```rust\nfn id(&self) -> Option<ElementId> {\n    Some(self.id.clone())\n}\n\n// Or if no ID needed\nfn id(&self) -> Option<ElementId> {\n    None\n}\n```\n\n### source_location()\n\nReturns source location for debugging. Usually returns `None` unless debugging is needed.\n\n```rust\nfn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n    None\n}\n```\n\n### request_layout()\n\nCalculates sizes and positions for the element tree.\n\n**Parameters:**\n- `global_id`: Global element identifier (optional)\n- `inspector_id`: Inspector element identifier (optional)\n- `window`: Mutable window reference\n- `cx`: Mutable app context\n\n**Returns:**\n- `(LayoutId, Self::RequestLayoutState)`: Layout ID and state for next phases\n\n**Responsibilities:**\n1. Calculate child layouts by calling `child.request_layout()`\n2. Create own layout using `window.request_layout()`\n3. Return layout ID and state to pass to next phases\n\n**Example:**\n```rust\nfn request_layout(\n    &mut self,\n    global_id: Option<&GlobalElementId>,\n    inspector_id: Option<&InspectorElementId>,\n    window: &mut Window,\n    cx: &mut App,\n) -> (LayoutId, Self::RequestLayoutState) {\n    // 1. Calculate child layouts\n    let child_layout_id = self.child.request_layout(\n        global_id,\n        inspector_id,\n        window,\n        cx\n    ).0;\n\n    // 2. Create own layout\n    let layout_id = window.request_layout(\n        Style {\n            size: size(px(200.), px(100.)),\n            ..default()\n        },\n        vec![child_layout_id],\n        cx\n    );\n\n    // 3. Return layout ID and state\n    (layout_id, MyLayoutState { child_layout_id })\n}\n```\n\n### prepaint()\n\nPrepares for painting by creating hitboxes and computing final bounds.\n\n**Parameters:**\n- `global_id`: Global element identifier (optional)\n- `inspector_id`: Inspector element identifier (optional)\n- `bounds`: Final bounds calculated by layout engine\n- `request_layout`: Mutable reference to layout state\n- `window`: Mutable window reference\n- `cx`: Mutable app context\n\n**Returns:**\n- `Self::PrepaintState`: State for paint phase\n\n**Responsibilities:**\n1. Compute final child bounds based on layout bounds\n2. Call `child.prepaint()` for all children\n3. Create hitboxes using `window.insert_hitbox()`\n4. Return state for paint phase\n\n**Example:**\n```rust\nfn prepaint(\n    &mut self,\n    global_id: Option<&GlobalElementId>,\n    inspector_id: Option<&InspectorElementId>,\n    bounds: Bounds<Pixels>,\n    request_layout: &mut Self::RequestLayoutState,\n    window: &mut Window,\n    cx: &mut App,\n) -> Self::PrepaintState {\n    // 1. Compute child bounds\n    let child_bounds = bounds; // or calculated subset\n\n    // 2. Prepaint children\n    self.child.prepaint(\n        global_id,\n        inspector_id,\n        child_bounds,\n        &mut request_layout.child_state,\n        window,\n        cx\n    );\n\n    // 3. Create hitboxes\n    let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);\n\n    // 4. Return paint state\n    MyPaintState { hitbox }\n}\n```\n\n### paint()\n\nRenders the element and handles interactions.\n\n**Parameters:**\n- `global_id`: Global element identifier (optional)\n- `inspector_id`: Inspector element identifier (optional)\n- `bounds`: Final bounds for rendering\n- `request_layout`: Mutable reference to layout state\n- `prepaint`: Mutable reference to prepaint state\n- `window`: Mutable window reference\n- `cx`: Mutable app context\n\n**Responsibilities:**\n1. Paint children first (bottom to top)\n2. Paint own content (backgrounds, borders, etc.)\n3. Set up interactions (mouse events, cursor styles)\n\n**Example:**\n```rust\nfn paint(\n    &mut self,\n    global_id: Option<&GlobalElementId>,\n    inspector_id: Option<&InspectorElementId>,\n    bounds: Bounds<Pixels>,\n    request_layout: &mut Self::RequestLayoutState,\n    prepaint: &mut Self::PrepaintState,\n    window: &mut Window,\n    cx: &mut App,\n) {\n    // 1. Paint children first\n    self.child.paint(\n        global_id,\n        inspector_id,\n        child_bounds,\n        &mut request_layout.child_state,\n        &mut prepaint.child_paint_state,\n        window,\n        cx\n    );\n\n    // 2. Paint own content\n    window.paint_quad(paint_quad(\n        bounds,\n        Corners::all(px(4.)),\n        cx.theme().background,\n    ));\n\n    // 3. Set up interactions\n    window.on_mouse_event({\n        let hitbox = prepaint.hitbox.clone();\n        move |event: &MouseDownEvent, phase, window, cx| {\n            if hitbox.is_hovered(window) && phase.bubble() {\n                // Handle click\n                cx.stop_propagation();\n            }\n        }\n    });\n\n    window.set_cursor_style(CursorStyle::PointingHand, &prepaint.hitbox);\n}\n```\n\n## IntoElement Integration\n\nElements must also implement `IntoElement` to be used as children:\n\n```rust\nimpl IntoElement for MyElement {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n```\n\nThis allows your custom element to be used directly in the element tree:\n\n```rust\ndiv()\n    .child(MyElement::new()) // Works because of IntoElement\n```\n\n## Common Parameters\n\n### Global and Inspector IDs\n\nBoth are optional identifiers used for debugging and inspection:\n- `global_id`: Unique identifier across entire app\n- `inspector_id`: Identifier for dev tools/inspector\n\nUsually passed through to children without modification.\n\n### Window and Context\n\n- `window: &mut Window`: Window-specific operations (painting, hitboxes, events)\n- `cx: &mut App`: App-wide operations (spawning tasks, accessing globals)\n\n## Layout System Integration\n\n### window.request_layout()\n\nCreates a layout node with specified style and children:\n\n```rust\nlet layout_id = window.request_layout(\n    Style {\n        size: size(px(200.), px(100.)),\n        flex: Flex::Column,\n        gap: px(8.),\n        ..default()\n    },\n    vec![child1_layout_id, child2_layout_id],\n    cx\n);\n```\n\n### Bounds<Pixels>\n\nRepresents rectangular region:\n\n```rust\npub struct Bounds<T> {\n    pub origin: Point<T>,\n    pub size: Size<T>,\n}\n\n// Create bounds\nlet bounds = Bounds::new(\n    point(px(10.), px(20.)),\n    size(px(100.), px(50.))\n);\n\n// Access properties\nbounds.left()    // origin.x\nbounds.top()     // origin.y\nbounds.right()   // origin.x + size.width\nbounds.bottom()  // origin.y + size.height\nbounds.center()  // center point\n```\n\n## Hitbox System\n\n### Creating Hitboxes\n\n```rust\n// Normal hitbox (blocks events)\nlet hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);\n\n// Transparent hitbox (passes events through)\nlet hitbox = window.insert_hitbox(bounds, HitboxBehavior::Transparent);\n```\n\n### Using Hitboxes\n\n```rust\n// Check if hovered\nif hitbox.is_hovered(window) {\n    // ...\n}\n\n// Set cursor style\nwindow.set_cursor_style(CursorStyle::PointingHand, &hitbox);\n\n// Use in event handlers\nwindow.on_mouse_event(move |event, phase, window, cx| {\n    if hitbox.is_hovered(window) && phase.bubble() {\n        // Handle event\n    }\n});\n```\n\n## Event Handling\n\n### Mouse Events\n\n```rust\n// Mouse down\nwindow.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {\n    if phase.bubble() && bounds.contains(&event.position) {\n        // Handle mouse down\n        cx.stop_propagation(); // Prevent bubbling\n    }\n});\n\n// Mouse up\nwindow.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {\n    // Handle mouse up\n});\n\n// Mouse move\nwindow.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {\n    // Handle mouse move\n});\n\n// Scroll\nwindow.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {\n    // Handle scroll\n});\n```\n\n### Event Phase\n\nEvents go through two phases:\n- **Capture**: Top-down (parent → child)\n- **Bubble**: Bottom-up (child → parent)\n\n```rust\nmove |event, phase, window, cx| {\n    if phase.capture() {\n        // Handle in capture phase\n    } else if phase.bubble() {\n        // Handle in bubble phase\n    }\n\n    cx.stop_propagation(); // Stop event from continuing\n}\n```\n\n## Cursor Styles\n\nAvailable cursor styles:\n\n```rust\nCursorStyle::Arrow\nCursorStyle::IBeam           // Text selection\nCursorStyle::PointingHand    // Clickable\nCursorStyle::ResizeLeft\nCursorStyle::ResizeRight\nCursorStyle::ResizeUp\nCursorStyle::ResizeDown\nCursorStyle::ResizeLeftRight\nCursorStyle::ResizeUpDown\nCursorStyle::Crosshair\nCursorStyle::OperationNotAllowed\n```\n\nUsage:\n\n```rust\nwindow.set_cursor_style(CursorStyle::PointingHand, &hitbox);\n```\n"
  },
  {
    "path": ".claude/skills/gpui-element/references/best-practices.md",
    "content": "# Element Best Practices\n\nGuidelines and best practices for implementing high-quality GPUI elements.\n\n## State Management\n\n### Using Associated Types Effectively\n\n**Good:** Use associated types to pass meaningful data between phases\n\n```rust\n// Good: Structured state with type safety\ntype RequestLayoutState = (StyledText, Vec<ChildLayout>);\ntype PrepaintState = (Hitbox, Vec<ChildBounds>);\n```\n\n**Bad:** Using empty state when you need data\n\n```rust\n// Bad: No state when you need to pass data\ntype RequestLayoutState = ();\ntype PrepaintState = ();\n// Now you can't pass layout info to paint phase!\n```\n\n### Managing Complex State\n\nFor elements with complex state, create dedicated structs:\n\n```rust\n// Good: Dedicated struct for complex state\npub struct TextElementState {\n    pub styled_text: StyledText,\n    pub text_layout: TextLayout,\n    pub child_states: Vec<ChildState>,\n}\n\ntype RequestLayoutState = TextElementState;\n```\n\n**Benefits:**\n- Clear documentation of state structure\n- Easy to extend\n- Type-safe access\n\n### State Lifecycle\n\n**Golden Rule:** State flows in one direction through the phases\n\n```\nrequest_layout → RequestLayoutState →\nprepaint → PrepaintState →\npaint\n```\n\n**Don't:**\n- Store state in the element struct that should be in associated types\n- Try to mutate element state in paint phase (use `cx.notify()` to schedule re-render)\n- Pass mutable references across phase boundaries\n\n## Performance Considerations\n\n### Minimize Allocations in Paint Phase\n\n**Critical:** Paint phase is called every frame during animations. Minimize allocations.\n\n**Good:** Pre-allocate in `request_layout` or `prepaint`\n\n```rust\nimpl Element for MyElement {\n    fn request_layout(&mut self, .., window: &mut Window, cx: &mut App)\n        -> (LayoutId, Vec<StyledText>)\n    {\n        // Allocate once during layout\n        let styled_texts = self.children\n            .iter()\n            .map(|child| StyledText::new(child.text.clone()))\n            .collect();\n\n        (layout_id, styled_texts)\n    }\n\n    fn paint(&mut self, .., styled_texts: &mut Vec<StyledText>, ..) {\n        // Just use pre-allocated styled_texts\n        for text in styled_texts {\n            text.paint(..);\n        }\n    }\n}\n```\n\n**Bad:** Allocate in `paint` phase\n\n```rust\nfn paint(&mut self, ..) {\n    // Bad: Allocation in paint phase!\n    let styled_texts: Vec<_> = self.children\n        .iter()\n        .map(|child| StyledText::new(child.text.clone()))\n        .collect();\n}\n```\n\n### Cache Expensive Computations\n\nUse memoization for expensive operations:\n\n```rust\npub struct CachedElement {\n    // Cache key\n    last_text: Option<SharedString>,\n    last_width: Option<Pixels>,\n\n    // Cached result\n    cached_layout: Option<TextLayout>,\n}\n\nimpl Element for CachedElement {\n    fn request_layout(&mut self, .., window: &mut Window, cx: &mut App)\n        -> (LayoutId, TextLayout)\n    {\n        let current_width = window.bounds().width();\n\n        // Check if cache is valid\n        if self.last_text.as_ref() != Some(&self.text)\n            || self.last_width != Some(current_width)\n            || self.cached_layout.is_none()\n        {\n            // Recompute expensive layout\n            self.cached_layout = Some(self.compute_text_layout(current_width));\n            self.last_text = Some(self.text.clone());\n            self.last_width = Some(current_width);\n        }\n\n        // Use cached layout\n        let layout = self.cached_layout.as_ref().unwrap();\n        (layout_id, layout.clone())\n    }\n}\n```\n\n### Lazy Child Rendering\n\nOnly render visible children in scrollable containers:\n\n```rust\nfn paint(&mut self, .., bounds: Bounds<Pixels>, paint_state: &mut Self::PrepaintState, ..) {\n    for (i, child) in self.children.iter_mut().enumerate() {\n        let child_bounds = paint_state.child_bounds[i];\n\n        // Only paint visible children\n        if self.is_visible(&child_bounds, &bounds) {\n            child.paint(..);\n        }\n    }\n}\n\nfn is_visible(&self, child_bounds: &Bounds<Pixels>, container_bounds: &Bounds<Pixels>) -> bool {\n    child_bounds.bottom() >= container_bounds.top() &&\n    child_bounds.top() <= container_bounds.bottom()\n}\n```\n\n## Interaction Handling\n\n### Proper Event Bubbling\n\nAlways check phase and bounds before handling events:\n\n```rust\nfn paint(&mut self, .., window: &mut Window, cx: &mut App) {\n    window.on_mouse_event({\n        let hitbox = self.hitbox.clone();\n        move |event: &MouseDownEvent, phase, window, cx| {\n            // Check phase first\n            if !phase.bubble() {\n                return;\n            }\n\n            // Check if event is within bounds\n            if !hitbox.is_hovered(window) {\n                return;\n            }\n\n            // Handle event\n            self.handle_click(event);\n\n            // Stop propagation if handled\n            cx.stop_propagation();\n        }\n    });\n}\n```\n\n**Don't forget:**\n- Check `phase.bubble()` or `phase.capture()` as appropriate\n- Check hitbox hover state or bounds\n- Call `cx.stop_propagation()` if you handle the event\n\n### Hitbox Management\n\nCreate hitboxes in `prepaint` phase, not `paint`:\n\n**Good:**\n\n```rust\nfn prepaint(&mut self, .., bounds: Bounds<Pixels>, window: &mut Window, ..) -> Hitbox {\n    // Create hitbox in prepaint\n    window.insert_hitbox(bounds, HitboxBehavior::Normal)\n}\n\nfn paint(&mut self, .., hitbox: &mut Hitbox, window: &mut Window, ..) {\n    // Use hitbox in paint\n    window.set_cursor_style(CursorStyle::PointingHand, hitbox);\n}\n```\n\n**Hitbox Behaviors:**\n\n```rust\n// Normal: Blocks events from passing through\nHitboxBehavior::Normal\n\n// Transparent: Allows events to pass through to elements below\nHitboxBehavior::Transparent\n```\n\n### Cursor Style Guidelines\n\nSet appropriate cursor styles for interactivity cues:\n\n```rust\n// Text selection\nwindow.set_cursor_style(CursorStyle::IBeam, &hitbox);\n\n// Clickable elements (desktop convention: use default, not pointing hand)\nwindow.set_cursor_style(CursorStyle::Arrow, &hitbox);\n\n// Links (web convention: use pointing hand)\nwindow.set_cursor_style(CursorStyle::PointingHand, &hitbox);\n\n// Resizable edges\nwindow.set_cursor_style(CursorStyle::ResizeLeftRight, &hitbox);\n```\n\n**Desktop vs Web Convention:**\n- Desktop apps: Use `Arrow` for buttons\n- Web apps: Use `PointingHand` for links only\n\n## Layout Strategies\n\n### Fixed Size Elements\n\nFor elements with known, unchanging size:\n\n```rust\nfn request_layout(&mut self, .., window: &mut Window, cx: &mut App) -> (LayoutId, ()) {\n    let layout_id = window.request_layout(\n        Style {\n            size: size(px(200.), px(100.)),\n            ..default()\n        },\n        vec![], // No children\n        cx\n    );\n    (layout_id, ())\n}\n```\n\n### Content-Based Sizing\n\nFor elements sized by their content:\n\n```rust\nfn request_layout(&mut self, .., window: &mut Window, cx: &mut App)\n    -> (LayoutId, Size<Pixels>)\n{\n    // Measure content\n    let text_bounds = self.measure_text(window);\n    let padding = px(16.);\n\n    let layout_id = window.request_layout(\n        Style {\n            size: size(\n                text_bounds.width() + padding * 2.,\n                text_bounds.height() + padding * 2.,\n            ),\n            ..default()\n        },\n        vec![],\n        cx\n    );\n\n    (layout_id, text_bounds)\n}\n```\n\n### Flexible Layouts\n\nFor elements that adapt to available space:\n\n```rust\nfn request_layout(&mut self, .., window: &mut Window, cx: &mut App)\n    -> (LayoutId, Vec<LayoutId>)\n{\n    let mut child_layout_ids = Vec::new();\n\n    for child in &mut self.children {\n        let (layout_id, _) = child.request_layout(window, cx);\n        child_layout_ids.push(layout_id);\n    }\n\n    let layout_id = window.request_layout(\n        Style {\n            flex_direction: FlexDirection::Row,\n            gap: px(8.),\n            size: Size {\n                width: relative(1.0),  // Fill parent width\n                height: auto(),        // Auto height\n            },\n            ..default()\n        },\n        child_layout_ids.clone(),\n        cx\n    );\n\n    (layout_id, child_layout_ids)\n}\n```\n\n## Error Handling\n\n### Graceful Degradation\n\nHandle errors gracefully, don't panic:\n\n```rust\nfn request_layout(&mut self, .., window: &mut Window, cx: &mut App)\n    -> (LayoutId, Option<TextLayout>)\n{\n    // Try to create styled text\n    match StyledText::new(self.text.clone()).request_layout(None, None, window, cx) {\n        Ok((layout_id, text_layout)) => {\n            (layout_id, Some(text_layout))\n        }\n        Err(e) => {\n            // Log error\n            eprintln!(\"Failed to layout text: {}\", e);\n\n            // Fallback to simple text\n            let fallback_text = StyledText::new(\"(Error loading text)\".into());\n            let (layout_id, _) = fallback_text.request_layout(None, None, window, cx);\n            (layout_id, None)\n        }\n    }\n}\n```\n\n### Defensive Bounds Checking\n\nAlways validate bounds and indices:\n\n```rust\nfn paint_selection(&self, selection: &Selection, text_layout: &TextLayout, ..) {\n    // Validate selection bounds\n    let start = selection.start.min(self.text.len());\n    let end = selection.end.min(self.text.len());\n\n    if start >= end {\n        return; // Invalid selection\n    }\n\n    let rects = text_layout.rects_for_range(start..end);\n    // Paint selection...\n}\n```\n\n## Testing Element Implementations\n\n### Layout Tests\n\nTest that layout calculations are correct:\n\n```rust\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use gpui::TestAppContext;\n\n    #[gpui::test]\n    fn test_element_layout(cx: &mut TestAppContext) {\n        cx.update(|cx| {\n            let mut window = cx.open_window(Default::default(), |_, _| ()).unwrap();\n\n            window.update(cx, |window, cx| {\n                let mut element = MyElement::new();\n                let (layout_id, layout_state) = element.request_layout(\n                    None,\n                    None,\n                    window,\n                    cx\n                );\n\n                // Assert layout properties\n                let bounds = window.layout_bounds(layout_id);\n                assert_eq!(bounds.size.width, px(200.));\n                assert_eq!(bounds.size.height, px(100.));\n            });\n        });\n    }\n}\n```\n\n### Interaction Tests\n\nTest that interactions work correctly:\n\n```rust\n#[gpui::test]\nfn test_element_click(cx: &mut TestAppContext) {\n    cx.update(|cx| {\n        let mut window = cx.open_window(Default::default(), |_, cx| {\n            cx.new(|_| MyElement::new())\n        }).unwrap();\n\n        window.update(cx, |window, cx| {\n            let view = window.root_view().unwrap();\n\n            // Simulate click\n            let position = point(px(10.), px(10.));\n            window.dispatch_event(MouseDownEvent {\n                position,\n                button: MouseButton::Left,\n                modifiers: Modifiers::default(),\n            });\n\n            // Assert element responded\n            view.read(cx).assert_clicked();\n        });\n    });\n}\n```\n\n## Common Pitfalls\n\n### ❌ Storing Layout State in Element Struct\n\n**Bad:**\n\n```rust\npub struct MyElement {\n    id: ElementId,\n    // Bad: This should be in RequestLayoutState\n    cached_layout: Option<TextLayout>,\n}\n```\n\n**Good:**\n\n```rust\npub struct MyElement {\n    id: ElementId,\n    text: SharedString,\n}\n\ntype RequestLayoutState = TextLayout; // Good: State in associated type\n```\n\n### ❌ Mutating Element in Paint Phase\n\n**Bad:**\n\n```rust\nfn paint(&mut self, ..) {\n    self.counter += 1; // Bad: Mutating element in paint\n}\n```\n\n**Good:**\n\n```rust\nfn paint(&mut self, .., window: &mut Window, cx: &mut App) {\n    window.on_mouse_event(move |event, phase, window, cx| {\n        if phase.bubble() {\n            self.counter += 1;\n            cx.notify(); // Schedule re-render\n        }\n    });\n}\n```\n\n### ❌ Creating Hitboxes in Paint Phase\n\n**Bad:**\n\n```rust\nfn paint(&mut self, .., bounds: Bounds<Pixels>, window: &mut Window, ..) {\n    // Bad: Creating hitbox in paint\n    let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);\n}\n```\n\n**Good:**\n\n```rust\nfn prepaint(&mut self, .., bounds: Bounds<Pixels>, window: &mut Window, ..) -> Hitbox {\n    // Good: Creating hitbox in prepaint\n    window.insert_hitbox(bounds, HitboxBehavior::Normal)\n}\n```\n\n### ❌ Ignoring Event Phase\n\n**Bad:**\n\n```rust\nwindow.on_mouse_event(move |event, phase, window, cx| {\n    // Bad: Not checking phase\n    self.handle_click(event);\n});\n```\n\n**Good:**\n\n```rust\nwindow.on_mouse_event(move |event, phase, window, cx| {\n    // Good: Checking phase\n    if !phase.bubble() {\n        return;\n    }\n    self.handle_click(event);\n});\n```\n\n## Performance Checklist\n\nBefore shipping an element implementation, verify:\n\n- [ ] No allocations in `paint` phase (except event handlers)\n- [ ] Expensive computations are cached/memoized\n- [ ] Only visible children are rendered in scrollable containers\n- [ ] Hitboxes created in `prepaint`, not `paint`\n- [ ] Event handlers check phase and bounds\n- [ ] Layout state is passed through associated types, not stored in element\n- [ ] Element implements proper error handling with fallbacks\n- [ ] Tests cover layout calculations and interactions\n"
  },
  {
    "path": ".claude/skills/gpui-element/references/examples.md",
    "content": "# Element Implementation Examples\n\nComplete examples of implementing custom elements for various scenarios.\n\n## Table of Contents\n\n1. [Simple Text Element](#simple-text-element)\n2. [Interactive Element with Selection](#interactive-element-with-selection)\n3. [Complex Element with Child Management](#complex-element-with-child-management)\n\n## Simple Text Element\n\nA basic text element with syntax highlighting support.\n\n```rust\npub struct SimpleText {\n    id: ElementId,\n    text: SharedString,\n    highlights: Vec<(Range<usize>, HighlightStyle)>,\n}\n\nimpl IntoElement for SimpleText {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\nimpl Element for SimpleText {\n    type RequestLayoutState = StyledText;\n    type PrepaintState = Hitbox;\n\n    fn id(&self) -> Option<ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App\n    ) -> (LayoutId, Self::RequestLayoutState) {\n        // Create styled text with highlights\n        let mut runs = Vec::new();\n        let mut ix = 0;\n\n        for (range, highlight) in &self.highlights {\n            // Add unstyled text before highlight\n            if ix < range.start {\n                runs.push(window.text_style().to_run(range.start - ix));\n            }\n\n            // Add highlighted text\n            runs.push(\n                window.text_style()\n                    .highlight(*highlight)\n                    .to_run(range.len())\n            );\n            ix = range.end;\n        }\n\n        // Add remaining unstyled text\n        if ix < self.text.len() {\n            runs.push(window.text_style().to_run(self.text.len() - ix));\n        }\n\n        let styled_text = StyledText::new(self.text.clone()).with_runs(runs);\n        let (layout_id, _) = styled_text.request_layout(\n            global_id,\n            inspector_id,\n            window,\n            cx\n        );\n\n        (layout_id, styled_text)\n    }\n\n    fn prepaint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        styled_text: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App\n    ) -> Self::PrepaintState {\n        // Prepaint the styled text\n        styled_text.prepaint(\n            global_id,\n            inspector_id,\n            bounds,\n            &mut (),\n            window,\n            cx\n        );\n\n        // Create hitbox for interaction\n        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);\n        hitbox\n    }\n\n    fn paint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        styled_text: &mut Self::RequestLayoutState,\n        hitbox: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App\n    ) {\n        // Paint the styled text\n        styled_text.paint(\n            global_id,\n            inspector_id,\n            bounds,\n            &mut (),\n            &mut (),\n            window,\n            cx\n        );\n\n        // Set cursor style for text\n        window.set_cursor_style(CursorStyle::IBeam, hitbox);\n    }\n}\n```\n\n## Interactive Element with Selection\n\nA text element that supports text selection via mouse interaction.\n\n```rust\n#[derive(Clone)]\npub struct Selection {\n    pub start: usize,\n    pub end: usize,\n}\n\npub struct SelectableText {\n    id: ElementId,\n    text: SharedString,\n    selectable: bool,\n    selection: Option<Selection>,\n}\n\nimpl IntoElement for SelectableText {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\nimpl Element for SelectableText {\n    type RequestLayoutState = TextLayout;\n    type PrepaintState = Option<Hitbox>;\n\n    fn id(&self) -> Option<ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App\n    ) -> (LayoutId, Self::RequestLayoutState) {\n        let styled_text = StyledText::new(self.text.clone());\n        let (layout_id, _) = styled_text.request_layout(\n            global_id,\n            inspector_id,\n            window,\n            cx\n        );\n\n        // Extract text layout for selection painting\n        let text_layout = styled_text.layout().clone();\n\n        (layout_id, text_layout)\n    }\n\n    fn prepaint(\n        &mut self,\n        _global_id: Option<&GlobalElementId>,\n        _inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        _text_layout: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        _cx: &mut App\n    ) -> Self::PrepaintState {\n        // Only create hitbox if selectable\n        if self.selectable {\n            Some(window.insert_hitbox(bounds, HitboxBehavior::Normal))\n        } else {\n            None\n        }\n    }\n\n    fn paint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        text_layout: &mut Self::RequestLayoutState,\n        hitbox: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App\n    ) {\n        // Paint text\n        let styled_text = StyledText::new(self.text.clone());\n        styled_text.paint(\n            global_id,\n            inspector_id,\n            bounds,\n            &mut (),\n            &mut (),\n            window,\n            cx\n        );\n\n        // Paint selection if any\n        if let Some(selection) = &self.selection {\n            Self::paint_selection(selection, text_layout, &bounds, window, cx);\n        }\n\n        // Handle mouse events for selection\n        if let Some(hitbox) = hitbox {\n            window.set_cursor_style(CursorStyle::IBeam, hitbox);\n\n            // Mouse down to start selection\n            window.on_mouse_event({\n                let bounds = bounds.clone();\n                move |event: &MouseDownEvent, phase, window, cx| {\n                    if bounds.contains(&event.position) && phase.bubble() {\n                        // Start selection at mouse position\n                        let char_index = Self::position_to_index(\n                            event.position,\n                            &bounds,\n                            text_layout\n                        );\n                        self.selection = Some(Selection {\n                            start: char_index,\n                            end: char_index,\n                        });\n                        cx.notify();\n                        cx.stop_propagation();\n                    }\n                }\n            });\n\n            // Mouse drag to extend selection\n            window.on_mouse_event({\n                let bounds = bounds.clone();\n                move |event: &MouseMoveEvent, phase, window, cx| {\n                    if let Some(selection) = &mut self.selection {\n                        if phase.bubble() {\n                            let char_index = Self::position_to_index(\n                                event.position,\n                                &bounds,\n                                text_layout\n                            );\n                            selection.end = char_index;\n                            cx.notify();\n                        }\n                    }\n                }\n            });\n        }\n    }\n}\n\nimpl SelectableText {\n    fn paint_selection(\n        selection: &Selection,\n        text_layout: &TextLayout,\n        bounds: &Bounds<Pixels>,\n        window: &mut Window,\n        cx: &mut App\n    ) {\n        // Calculate selection bounds from text layout\n        let selection_rects = text_layout.rects_for_range(\n            selection.start..selection.end\n        );\n\n        // Paint selection background\n        for rect in selection_rects {\n            window.paint_quad(paint_quad(\n                Bounds::new(\n                    point(bounds.left() + rect.origin.x, bounds.top() + rect.origin.y),\n                    rect.size\n                ),\n                Corners::default(),\n                cx.theme().selection_background,\n            ));\n        }\n    }\n\n    fn position_to_index(\n        position: Point<Pixels>,\n        bounds: &Bounds<Pixels>,\n        text_layout: &TextLayout\n    ) -> usize {\n        // Convert screen position to character index\n        let relative_pos = point(\n            position.x - bounds.left(),\n            position.y - bounds.top()\n        );\n        text_layout.index_for_position(relative_pos)\n    }\n}\n```\n\n## Complex Element with Child Management\n\nA container element that manages multiple children with scrolling support.\n\n```rust\npub struct ComplexElement {\n    id: ElementId,\n    children: Vec<Box<dyn Element<RequestLayoutState = (), PrepaintState = ()>>>,\n    scrollable: bool,\n    scroll_offset: Point<Pixels>,\n}\n\nstruct ComplexLayoutState {\n    child_layouts: Vec<LayoutId>,\n    total_height: Pixels,\n}\n\nstruct ComplexPaintState {\n    child_bounds: Vec<Bounds<Pixels>>,\n    hitbox: Hitbox,\n}\n\nimpl IntoElement for ComplexElement {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\nimpl Element for ComplexElement {\n    type RequestLayoutState = ComplexLayoutState;\n    type PrepaintState = ComplexPaintState;\n\n    fn id(&self) -> Option<ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App\n    ) -> (LayoutId, Self::RequestLayoutState) {\n        let mut child_layouts = Vec::new();\n        let mut total_height = px(0.);\n\n        // Request layout for all children\n        for child in &mut self.children {\n            let (child_layout_id, _) = child.request_layout(\n                global_id,\n                inspector_id,\n                window,\n                cx\n            );\n            child_layouts.push(child_layout_id);\n\n            // Get child size from layout\n            let child_size = window.layout_bounds(child_layout_id).size();\n            total_height += child_size.height;\n        }\n\n        // Create container layout\n        let layout_id = window.request_layout(\n            Style {\n                flex_direction: FlexDirection::Column,\n                gap: px(8.),\n                size: Size {\n                    width: relative(1.0),\n                    height: if self.scrollable {\n                        // Fixed height for scrollable\n                        px(400.)\n                    } else {\n                        // Auto height for non-scrollable\n                        total_height\n                    },\n                },\n                ..default()\n            },\n            child_layouts.clone(),\n            cx\n        );\n\n        (layout_id, ComplexLayoutState {\n            child_layouts,\n            total_height,\n        })\n    }\n\n    fn prepaint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        layout_state: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App\n    ) -> Self::PrepaintState {\n        let mut child_bounds = Vec::new();\n        let mut y_offset = self.scroll_offset.y;\n\n        // Calculate child bounds and prepaint children\n        for (child, layout_id) in self.children.iter_mut()\n            .zip(&layout_state.child_layouts)\n        {\n            let child_size = window.layout_bounds(*layout_id).size();\n            let child_bound = Bounds::new(\n                point(bounds.left(), bounds.top() + y_offset),\n                child_size\n            );\n\n            // Only prepaint visible children\n            if self.is_visible(&child_bound, &bounds) {\n                child.prepaint(\n                    global_id,\n                    inspector_id,\n                    child_bound,\n                    &mut (),\n                    window,\n                    cx\n                );\n            }\n\n            child_bounds.push(child_bound);\n            y_offset += child_size.height + px(8.); // gap\n        }\n\n        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);\n\n        ComplexPaintState {\n            child_bounds,\n            hitbox,\n        }\n    }\n\n    fn paint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        layout_state: &mut Self::RequestLayoutState,\n        paint_state: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App\n    ) {\n        // Paint background\n        window.paint_quad(paint_quad(\n            bounds,\n            Corners::all(px(4.)),\n            cx.theme().background,\n        ));\n\n        // Paint visible children only\n        for (i, child) in self.children.iter_mut().enumerate() {\n            let child_bounds = paint_state.child_bounds[i];\n\n            if self.is_visible(&child_bounds, &bounds) {\n                child.paint(\n                    global_id,\n                    inspector_id,\n                    child_bounds,\n                    &mut (),\n                    &mut (),\n                    window,\n                    cx\n                );\n            }\n        }\n\n        // Paint scrollbar if scrollable\n        if self.scrollable {\n            self.paint_scrollbar(bounds, layout_state, window, cx);\n        }\n\n        // Handle scroll events\n        if self.scrollable {\n            window.on_mouse_event({\n                let hitbox = paint_state.hitbox.clone();\n                let total_height = layout_state.total_height;\n                let visible_height = bounds.size.height;\n\n                move |event: &ScrollWheelEvent, phase, window, cx| {\n                    if hitbox.is_hovered(window) && phase.bubble() {\n                        // Update scroll offset\n                        self.scroll_offset.y -= event.delta.y;\n\n                        // Clamp scroll offset\n                        let max_scroll = (total_height - visible_height).max(px(0.));\n                        self.scroll_offset.y = self.scroll_offset.y\n                            .max(px(0.))\n                            .min(max_scroll);\n\n                        cx.notify();\n                        cx.stop_propagation();\n                    }\n                }\n            });\n        }\n    }\n}\n\nimpl ComplexElement {\n    fn is_visible(&self, child_bounds: &Bounds<Pixels>, container_bounds: &Bounds<Pixels>) -> bool {\n        // Check if child is within visible area\n        child_bounds.bottom() >= container_bounds.top() &&\n        child_bounds.top() <= container_bounds.bottom()\n    }\n\n    fn paint_scrollbar(\n        &self,\n        bounds: Bounds<Pixels>,\n        layout_state: &ComplexLayoutState,\n        window: &mut Window,\n        cx: &mut App\n    ) {\n        let scrollbar_width = px(8.);\n        let visible_height = bounds.size.height;\n        let total_height = layout_state.total_height;\n\n        if total_height <= visible_height {\n            return; // No need for scrollbar\n        }\n\n        // Calculate scrollbar position and size\n        let scroll_ratio = self.scroll_offset.y / (total_height - visible_height);\n        let thumb_height = (visible_height / total_height) * visible_height;\n        let thumb_y = scroll_ratio * (visible_height - thumb_height);\n\n        // Paint scrollbar track\n        let track_bounds = Bounds::new(\n            point(bounds.right() - scrollbar_width, bounds.top()),\n            size(scrollbar_width, visible_height)\n        );\n        window.paint_quad(paint_quad(\n            track_bounds,\n            Corners::default(),\n            cx.theme().scrollbar_track,\n        ));\n\n        // Paint scrollbar thumb\n        let thumb_bounds = Bounds::new(\n            point(bounds.right() - scrollbar_width, bounds.top() + thumb_y),\n            size(scrollbar_width, thumb_height)\n        );\n        window.paint_quad(paint_quad(\n            thumb_bounds,\n            Corners::all(px(4.)),\n            cx.theme().scrollbar_thumb,\n        ));\n    }\n}\n```\n\n## Usage Examples\n\n### Using SimpleText\n\n```rust\nfn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n    div()\n        .child(SimpleText {\n            id: ElementId::Name(\"code-text\".into()),\n            text: \"fn main() { println!(\\\"Hello\\\"); }\".into(),\n            highlights: vec![\n                (0..2, HighlightStyle::keyword()),\n                (3..7, HighlightStyle::function()),\n            ],\n        })\n}\n```\n\n### Using SelectableText\n\n```rust\nfn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n    div()\n        .child(SelectableText {\n            id: ElementId::Name(\"selectable-text\".into()),\n            text: \"Select this text with your mouse\".into(),\n            selectable: true,\n            selection: self.current_selection.clone(),\n        })\n}\n```\n\n### Using ComplexElement\n\n```rust\nfn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n    let children: Vec<Box<dyn Element<_, _>>> = self.items\n        .iter()\n        .map(|item| Box::new(div().child(item.name.clone())) as Box<_>)\n        .collect();\n\n    div()\n        .child(ComplexElement {\n            id: ElementId::Name(\"scrollable-list\".into()),\n            children,\n            scrollable: true,\n            scroll_offset: self.scroll_offset,\n        })\n}\n```\n"
  },
  {
    "path": ".claude/skills/gpui-element/references/patterns.md",
    "content": "# Common Element Patterns\n\nReusable patterns for implementing common element types in GPUI.\n\n## Text Rendering Elements\n\nElements that display and manipulate text content.\n\n### Pattern Characteristics\n\n- Use `StyledText` for text layout and rendering\n- Handle text selection in `paint` phase with hitbox interaction\n- Create hitboxes for text interaction in `prepaint`\n- Support text highlighting and custom styling via runs\n\n### Implementation Template\n\n```rust\npub struct TextElement {\n    id: ElementId,\n    text: SharedString,\n    style: TextStyle,\n}\n\nimpl Element for TextElement {\n    type RequestLayoutState = StyledText;\n    type PrepaintState = Hitbox;\n\n    fn request_layout(&mut self, .., window: &mut Window, cx: &mut App)\n        -> (LayoutId, StyledText)\n    {\n        let styled_text = StyledText::new(self.text.clone())\n            .with_style(self.style);\n        let (layout_id, _) = styled_text.request_layout(None, None, window, cx);\n        (layout_id, styled_text)\n    }\n\n    fn prepaint(&mut self, .., bounds: Bounds<Pixels>, styled_text: &mut StyledText,\n                window: &mut Window, cx: &mut App) -> Hitbox\n    {\n        styled_text.prepaint(None, None, bounds, &mut (), window, cx);\n        window.insert_hitbox(bounds, HitboxBehavior::Normal)\n    }\n\n    fn paint(&mut self, .., bounds: Bounds<Pixels>, styled_text: &mut StyledText,\n             hitbox: &mut Hitbox, window: &mut Window, cx: &mut App)\n    {\n        styled_text.paint(None, None, bounds, &mut (), &mut (), window, cx);\n        window.set_cursor_style(CursorStyle::IBeam, hitbox);\n    }\n}\n```\n\n### Use Cases\n\n- Code editors with syntax highlighting\n- Rich text displays\n- Labels with custom formatting\n- Selectable text areas\n\n## Container Elements\n\nElements that manage and layout child elements.\n\n### Pattern Characteristics\n\n- Manage child element layouts and positions\n- Handle scrolling and clipping when needed\n- Implement flex/grid-like layouts\n- Coordinate child interactions and event delegation\n\n### Implementation Template\n\n```rust\npub struct ContainerElement {\n    id: ElementId,\n    children: Vec<AnyElement>,\n    direction: FlexDirection,\n    gap: Pixels,\n}\n\nimpl Element for ContainerElement {\n    type RequestLayoutState = Vec<LayoutId>;\n    type PrepaintState = Vec<Bounds<Pixels>>;\n\n    fn request_layout(&mut self, .., window: &mut Window, cx: &mut App)\n        -> (LayoutId, Vec<LayoutId>)\n    {\n        let child_layout_ids: Vec<_> = self.children\n            .iter_mut()\n            .map(|child| child.request_layout(window, cx).0)\n            .collect();\n\n        let layout_id = window.request_layout(\n            Style {\n                flex_direction: self.direction,\n                gap: self.gap,\n                ..default()\n            },\n            child_layout_ids.clone(),\n            cx\n        );\n\n        (layout_id, child_layout_ids)\n    }\n\n    fn prepaint(&mut self, .., bounds: Bounds<Pixels>, layout_ids: &mut Vec<LayoutId>,\n                window: &mut Window, cx: &mut App) -> Vec<Bounds<Pixels>>\n    {\n        let mut child_bounds = Vec::new();\n\n        for (child, layout_id) in self.children.iter_mut().zip(layout_ids.iter()) {\n            let child_bound = window.layout_bounds(*layout_id);\n            child.prepaint(child_bound, window, cx);\n            child_bounds.push(child_bound);\n        }\n\n        child_bounds\n    }\n\n    fn paint(&mut self, .., child_bounds: &mut Vec<Bounds<Pixels>>,\n             window: &mut Window, cx: &mut App)\n    {\n        for (child, bounds) in self.children.iter_mut().zip(child_bounds.iter()) {\n            child.paint(*bounds, window, cx);\n        }\n    }\n}\n```\n\n### Use Cases\n\n- Panels and split views\n- List containers\n- Grid layouts\n- Tab containers\n\n## Interactive Elements\n\nElements that respond to user input (mouse, keyboard, touch).\n\n### Pattern Characteristics\n\n- Create appropriate hitboxes for interaction areas\n- Handle mouse/keyboard/touch events properly\n- Manage focus and cursor styles\n- Support hover, active, and disabled states\n\n### Implementation Template\n\n```rust\npub struct InteractiveElement {\n    id: ElementId,\n    content: AnyElement,\n    on_click: Option<Box<dyn Fn(&MouseUpEvent, &mut Window, &mut App)>>,\n    hover_style: Option<Style>,\n}\n\nimpl Element for InteractiveElement {\n    type RequestLayoutState = LayoutId;\n    type PrepaintState = (Hitbox, bool); // hitbox and is_hovered\n\n    fn request_layout(&mut self, .., window: &mut Window, cx: &mut App)\n        -> (LayoutId, LayoutId)\n    {\n        let (content_layout, _) = self.content.request_layout(window, cx);\n        (content_layout, content_layout)\n    }\n\n    fn prepaint(&mut self, .., bounds: Bounds<Pixels>, content_layout: &mut LayoutId,\n                window: &mut Window, cx: &mut App) -> (Hitbox, bool)\n    {\n        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);\n        let is_hovered = hitbox.is_hovered(window);\n\n        self.content.prepaint(bounds, window, cx);\n\n        (hitbox, is_hovered)\n    }\n\n    fn paint(&mut self, .., bounds: Bounds<Pixels>, content_layout: &mut LayoutId,\n             prepaint: &mut (Hitbox, bool), window: &mut Window, cx: &mut App)\n    {\n        let (hitbox, is_hovered) = prepaint;\n\n        // Paint hover background if hovered\n        if *is_hovered {\n            if let Some(hover_style) = &self.hover_style {\n                window.paint_quad(paint_quad(\n                    bounds,\n                    Corners::all(px(4.)),\n                    hover_style.background_color.unwrap_or(cx.theme().hover),\n                ));\n            }\n        }\n\n        // Paint content\n        self.content.paint(bounds, window, cx);\n\n        // Handle click\n        if let Some(on_click) = self.on_click.as_ref() {\n            window.on_mouse_event({\n                let on_click = on_click.clone();\n                let hitbox = hitbox.clone();\n                move |event: &MouseUpEvent, phase, window, cx| {\n                    if hitbox.is_hovered(window) && phase.bubble() {\n                        on_click(event, window, cx);\n                        cx.stop_propagation();\n                    }\n                }\n            });\n        }\n\n        // Set cursor style\n        window.set_cursor_style(CursorStyle::PointingHand, hitbox);\n    }\n}\n```\n\n### Use Cases\n\n- Buttons\n- Links\n- Clickable cards\n- Drag handles\n- Menu items\n\n## Composite Elements\n\nElements that combine multiple child elements with complex coordination.\n\n### Pattern Characteristics\n\n- Combine multiple child elements with different types\n- Manage complex state across children\n- Coordinate animations and transitions\n- Handle focus delegation between children\n\n### Implementation Template\n\n```rust\npub struct CompositeElement {\n    id: ElementId,\n    header: AnyElement,\n    content: AnyElement,\n    footer: Option<AnyElement>,\n}\n\nstruct CompositeLayoutState {\n    header_layout: LayoutId,\n    content_layout: LayoutId,\n    footer_layout: Option<LayoutId>,\n}\n\nstruct CompositePaintState {\n    header_bounds: Bounds<Pixels>,\n    content_bounds: Bounds<Pixels>,\n    footer_bounds: Option<Bounds<Pixels>>,\n}\n\nimpl Element for CompositeElement {\n    type RequestLayoutState = CompositeLayoutState;\n    type PrepaintState = CompositePaintState;\n\n    fn request_layout(&mut self, .., window: &mut Window, cx: &mut App)\n        -> (LayoutId, CompositeLayoutState)\n    {\n        let (header_layout, _) = self.header.request_layout(window, cx);\n        let (content_layout, _) = self.content.request_layout(window, cx);\n        let footer_layout = self.footer.as_mut()\n            .map(|f| f.request_layout(window, cx).0);\n\n        let mut children = vec![header_layout, content_layout];\n        if let Some(footer) = footer_layout {\n            children.push(footer);\n        }\n\n        let layout_id = window.request_layout(\n            Style {\n                flex_direction: FlexDirection::Column,\n                size: Size {\n                    width: relative(1.0),\n                    height: auto(),\n                },\n                ..default()\n            },\n            children,\n            cx\n        );\n\n        (layout_id, CompositeLayoutState {\n            header_layout,\n            content_layout,\n            footer_layout,\n        })\n    }\n\n    fn prepaint(&mut self, .., bounds: Bounds<Pixels>, layout: &mut CompositeLayoutState,\n                window: &mut Window, cx: &mut App) -> CompositePaintState\n    {\n        let header_bounds = window.layout_bounds(layout.header_layout);\n        let content_bounds = window.layout_bounds(layout.content_layout);\n        let footer_bounds = layout.footer_layout\n            .map(|id| window.layout_bounds(id));\n\n        self.header.prepaint(header_bounds, window, cx);\n        self.content.prepaint(content_bounds, window, cx);\n        if let (Some(footer), Some(bounds)) = (&mut self.footer, footer_bounds) {\n            footer.prepaint(bounds, window, cx);\n        }\n\n        CompositePaintState {\n            header_bounds,\n            content_bounds,\n            footer_bounds,\n        }\n    }\n\n    fn paint(&mut self, .., paint_state: &mut CompositePaintState,\n             window: &mut Window, cx: &mut App)\n    {\n        self.header.paint(paint_state.header_bounds, window, cx);\n        self.content.paint(paint_state.content_bounds, window, cx);\n        if let (Some(footer), Some(bounds)) = (&mut self.footer, paint_state.footer_bounds) {\n            footer.paint(bounds, window, cx);\n        }\n    }\n}\n```\n\n### Use Cases\n\n- Dialog boxes (header + content + footer)\n- Cards with multiple sections\n- Form layouts\n- Panels with toolbars\n\n## Scrollable Elements\n\nElements with scrollable content areas.\n\n### Pattern Characteristics\n\n- Manage scroll state (offset, velocity)\n- Handle scroll events (wheel, drag, touch)\n- Paint scrollbars (track and thumb)\n- Clip content to visible area\n\n### Implementation Template\n\n```rust\npub struct ScrollableElement {\n    id: ElementId,\n    content: AnyElement,\n    scroll_offset: Point<Pixels>,\n    content_size: Size<Pixels>,\n}\n\nstruct ScrollPaintState {\n    hitbox: Hitbox,\n    visible_bounds: Bounds<Pixels>,\n}\n\nimpl Element for ScrollableElement {\n    type RequestLayoutState = (LayoutId, Size<Pixels>);\n    type PrepaintState = ScrollPaintState;\n\n    fn request_layout(&mut self, .., window: &mut Window, cx: &mut App)\n        -> (LayoutId, (LayoutId, Size<Pixels>))\n    {\n        let (content_layout, _) = self.content.request_layout(window, cx);\n        let content_size = window.layout_bounds(content_layout).size;\n\n        let layout_id = window.request_layout(\n            Style {\n                size: Size {\n                    width: relative(1.0),\n                    height: px(400.), // Fixed viewport height\n                },\n                overflow: Overflow::Hidden,\n                ..default()\n            },\n            vec![content_layout],\n            cx\n        );\n\n        (layout_id, (content_layout, content_size))\n    }\n\n    fn prepaint(&mut self, .., bounds: Bounds<Pixels>, layout: &mut (LayoutId, Size<Pixels>),\n                window: &mut Window, cx: &mut App) -> ScrollPaintState\n    {\n        let (content_layout, content_size) = layout;\n\n        // Calculate content bounds with scroll offset\n        let content_bounds = Bounds::new(\n            point(bounds.left(), bounds.top() - self.scroll_offset.y),\n            *content_size\n        );\n\n        self.content.prepaint(content_bounds, window, cx);\n\n        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);\n\n        ScrollPaintState {\n            hitbox,\n            visible_bounds: bounds,\n        }\n    }\n\n    fn paint(&mut self, .., layout: &mut (LayoutId, Size<Pixels>),\n             paint_state: &mut ScrollPaintState, window: &mut Window, cx: &mut App)\n    {\n        let (_, content_size) = layout;\n\n        // Paint content\n        self.content.paint(paint_state.visible_bounds, window, cx);\n\n        // Paint scrollbar\n        self.paint_scrollbar(paint_state.visible_bounds, *content_size, window, cx);\n\n        // Handle scroll events\n        window.on_mouse_event({\n            let hitbox = paint_state.hitbox.clone();\n            let content_height = content_size.height;\n            let visible_height = paint_state.visible_bounds.size.height;\n\n            move |event: &ScrollWheelEvent, phase, window, cx| {\n                if hitbox.is_hovered(window) && phase.bubble() {\n                    // Update scroll offset\n                    self.scroll_offset.y -= event.delta.y;\n\n                    // Clamp to valid range\n                    let max_scroll = (content_height - visible_height).max(px(0.));\n                    self.scroll_offset.y = self.scroll_offset.y\n                        .max(px(0.))\n                        .min(max_scroll);\n\n                    cx.notify();\n                    cx.stop_propagation();\n                }\n            }\n        });\n    }\n}\n\nimpl ScrollableElement {\n    fn paint_scrollbar(\n        &self,\n        bounds: Bounds<Pixels>,\n        content_size: Size<Pixels>,\n        window: &mut Window,\n        cx: &mut App\n    ) {\n        let visible_height = bounds.size.height;\n        let content_height = content_size.height;\n\n        if content_height <= visible_height {\n            return; // No scrollbar needed\n        }\n\n        let scrollbar_width = px(8.);\n\n        // Calculate thumb position and size\n        let scroll_ratio = self.scroll_offset.y / (content_height - visible_height);\n        let thumb_height = (visible_height / content_height) * visible_height;\n        let thumb_y = scroll_ratio * (visible_height - thumb_height);\n\n        // Paint track\n        window.paint_quad(paint_quad(\n            Bounds::new(\n                point(bounds.right() - scrollbar_width, bounds.top()),\n                size(scrollbar_width, visible_height)\n            ),\n            Corners::default(),\n            cx.theme().scrollbar_track,\n        ));\n\n        // Paint thumb\n        window.paint_quad(paint_quad(\n            Bounds::new(\n                point(bounds.right() - scrollbar_width, bounds.top() + thumb_y),\n                size(scrollbar_width, thumb_height)\n            ),\n            Corners::all(px(4.)),\n            cx.theme().scrollbar_thumb,\n        ));\n    }\n}\n```\n\n### Use Cases\n\n- Scrollable lists\n- Code editors with large files\n- Long-form text content\n- Image galleries\n\n## Pattern Selection Guide\n\n| Need | Pattern | Complexity |\n|------|---------|------------|\n| Display styled text | Text Rendering | Low |\n| Layout multiple children | Container | Low-Medium |\n| Handle clicks/hovers | Interactive | Medium |\n| Complex multi-part UI | Composite | Medium-High |\n| Large content with scrolling | Scrollable | High |\n\nChoose the simplest pattern that meets your requirements, then extend as needed.\n"
  },
  {
    "path": ".claude/skills/gpui-entity/SKILL.md",
    "content": "---\nname: gpui-entity\ndescription: Entity management and state handling in GPUI. Use when working with entities, managing component state, coordinating between components, handling async operations with state updates, or implementing reactive patterns. Entities provide safe concurrent access to application state.\n---\n\n## Overview\n\nAn `Entity<T>` is a handle to state of type `T`, providing safe access and updates.\n\n**Key Methods:**\n- `entity.read(cx)` → `&T` - Read-only access\n- `entity.read_with(cx, |state, cx| ...)` → `R` - Read with closure\n- `entity.update(cx, |state, cx| ...)` → `R` - Mutable update\n- `entity.downgrade()` → `WeakEntity<T>` - Create weak reference\n- `entity.entity_id()` → `EntityId` - Unique identifier\n\n**Entity Types:**\n- **`Entity<T>`**: Strong reference (increases ref count)\n- **`WeakEntity<T>`**: Weak reference (doesn't prevent cleanup, returns `Result`)\n\n## Quick Start\n\n### Creating and Using Entities\n\n```rust\n// Create entity\nlet counter = cx.new(|cx| Counter { count: 0 });\n\n// Read state\nlet count = counter.read(cx).count;\n\n// Update state\ncounter.update(cx, |state, cx| {\n    state.count += 1;\n    cx.notify(); // Trigger re-render\n});\n\n// Weak reference (for closures/callbacks)\nlet weak = counter.downgrade();\nlet _ = weak.update(cx, |state, cx| {\n    state.count += 1;\n    cx.notify();\n});\n```\n\n### In Components\n\n```rust\nstruct MyComponent {\n    shared_state: Entity<SharedData>,\n}\n\nimpl MyComponent {\n    fn new(cx: &mut App) -> Entity<Self> {\n        let shared = cx.new(|_| SharedData::default());\n\n        cx.new(|cx| Self {\n            shared_state: shared,\n        })\n    }\n\n    fn update_shared(&mut self, cx: &mut Context<Self>) {\n        self.shared_state.update(cx, |state, cx| {\n            state.value = 42;\n            cx.notify();\n        });\n    }\n}\n```\n\n### Async Operations\n\n```rust\nimpl MyComponent {\n    fn fetch_data(&mut self, cx: &mut Context<Self>) {\n        let weak_self = cx.entity().downgrade();\n\n        cx.spawn(async move |cx| {\n            let data = fetch_from_api().await;\n\n            // Update entity safely\n            let _ = weak_self.update(cx, |state, cx| {\n                state.data = Some(data);\n                cx.notify();\n            });\n        }).detach();\n    }\n}\n```\n\n## Core Principles\n\n### Always Use Weak References in Closures\n\n```rust\n// ✅ Good: Weak reference prevents retain cycles\nlet weak = cx.entity().downgrade();\ncallback(move || {\n    let _ = weak.update(cx, |state, cx| cx.notify());\n});\n\n// ❌ Bad: Strong reference may cause memory leak\nlet strong = cx.entity();\ncallback(move || {\n    strong.update(cx, |state, cx| cx.notify());\n});\n```\n\n### Use Inner Context\n\n```rust\n// ✅ Good: Use inner cx from closure\nentity.update(cx, |state, inner_cx| {\n    inner_cx.notify(); // Correct\n});\n\n// ❌ Bad: Use outer cx (multiple borrow error)\nentity.update(cx, |state, inner_cx| {\n    cx.notify(); // Wrong!\n});\n```\n\n### Avoid Nested Updates\n\n```rust\n// ✅ Good: Sequential updates\nentity1.update(cx, |state, cx| { /* ... */ });\nentity2.update(cx, |state, cx| { /* ... */ });\n\n// ❌ Bad: Nested updates (may panic)\nentity1.update(cx, |_, cx| {\n    entity2.update(cx, |_, cx| { /* ... */ });\n});\n```\n\n## Common Use Cases\n\n1. **Component State**: Internal state that needs reactivity\n2. **Shared State**: State shared between multiple components\n3. **Parent-Child**: Coordinating between related components (use weak refs)\n4. **Async State**: Managing state that changes from async operations\n5. **Observations**: Reacting to changes in other entities\n\n## Reference Documentation\n\n### Complete API Documentation\n- **Entity API**: See [api-reference.md](references/api-reference.md)\n  - Entity types, methods, lifecycle\n  - Context methods, async operations\n  - Error handling, type conversions\n\n### Implementation Guides\n- **Patterns**: See [patterns.md](references/patterns.md)\n  - Model-view separation, state management\n  - Cross-entity communication, async operations\n  - Observer pattern, event subscription\n  - Pattern selection guide\n\n- **Best Practices**: See [best-practices.md](references/best-practices.md)\n  - Avoiding common pitfalls, memory leaks\n  - Performance optimization, batching updates\n  - Lifecycle management, cleanup\n  - Async best practices, testing\n\n- **Advanced Patterns**: See [advanced.md](references/advanced.md)\n  - Entity collections, registry pattern\n  - Debounced/throttled updates, state machines\n  - Entity snapshots, transactions, pools\n"
  },
  {
    "path": ".claude/skills/gpui-entity/references/advanced.md",
    "content": "# Advanced Entity Patterns\n\nAdvanced techniques for sophisticated entity management scenarios.\n\n## Entity Collections Management\n\n### Dynamic Collection with Cleanup\n\n```rust\nstruct EntityCollection<T> {\n    strong_refs: Vec<Entity<T>>,\n    weak_refs: Vec<WeakEntity<T>>,\n}\n\nimpl<T> EntityCollection<T> {\n    fn new() -> Self {\n        Self {\n            strong_refs: Vec::new(),\n            weak_refs: Vec::new(),\n        }\n    }\n\n    fn add(&mut self, entity: Entity<T>, cx: &mut App) {\n        self.strong_refs.push(entity.clone());\n        self.weak_refs.push(entity.downgrade());\n    }\n\n    fn remove(&mut self, entity_id: EntityId, cx: &mut App) {\n        self.strong_refs.retain(|e| e.entity_id() != entity_id);\n        self.weak_refs.retain(|w| {\n            w.upgrade()\n                .map(|e| e.entity_id() != entity_id)\n                .unwrap_or(false)\n        });\n    }\n\n    fn cleanup_invalid(&mut self, cx: &mut App) {\n        self.weak_refs.retain(|weak| weak.upgrade().is_some());\n    }\n\n    fn for_each<F>(&self, cx: &mut App, mut f: F)\n    where\n        F: FnMut(&Entity<T>, &mut App),\n    {\n        for entity in &self.strong_refs {\n            f(entity, cx);\n        }\n    }\n\n    fn for_each_weak<F>(&mut self, cx: &mut App, mut f: F)\n    where\n        F: FnMut(Entity<T>, &mut App),\n    {\n        self.weak_refs.retain(|weak| {\n            if let Some(entity) = weak.upgrade() {\n                f(entity, cx);\n                true\n            } else {\n                false // Remove invalid weak references\n            }\n        });\n    }\n}\n```\n\n### Entity Registry Pattern\n\n```rust\nuse std::collections::HashMap;\n\nstruct EntityRegistry<T> {\n    entities: HashMap<EntityId, WeakEntity<T>>,\n}\n\nimpl<T> EntityRegistry<T> {\n    fn new() -> Self {\n        Self {\n            entities: HashMap::new(),\n        }\n    }\n\n    fn register(&mut self, entity: &Entity<T>) {\n        self.entities.insert(entity.entity_id(), entity.downgrade());\n    }\n\n    fn unregister(&mut self, entity_id: EntityId) {\n        self.entities.remove(&entity_id);\n    }\n\n    fn get(&self, entity_id: EntityId) -> Option<Entity<T>> {\n        self.entities.get(&entity_id)?.upgrade()\n    }\n\n    fn cleanup(&mut self) {\n        self.entities.retain(|_, weak| weak.upgrade().is_some());\n    }\n\n    fn count(&self) -> usize {\n        self.entities.len()\n    }\n\n    fn all_entities(&self) -> Vec<Entity<T>> {\n        self.entities\n            .values()\n            .filter_map(|weak| weak.upgrade())\n            .collect()\n    }\n}\n```\n\n## Conditional Update Patterns\n\n### Debounced Updates\n\n```rust\nuse std::time::{Duration, Instant};\n\nstruct DebouncedEntity<T> {\n    entity: Entity<T>,\n    last_update: Instant,\n    debounce_duration: Duration,\n    pending_update: Option<Box<dyn FnOnce(&mut T, &mut Context<T>)>>,\n}\n\nimpl<T: 'static> DebouncedEntity<T> {\n    fn new(entity: Entity<T>, debounce_ms: u64) -> Self {\n        Self {\n            entity,\n            last_update: Instant::now(),\n            debounce_duration: Duration::from_millis(debounce_ms),\n            pending_update: None,\n        }\n    }\n\n    fn update<F>(&mut self, cx: &mut App, update_fn: F)\n    where\n        F: FnOnce(&mut T, &mut Context<T>) + 'static,\n    {\n        let now = Instant::now();\n        let elapsed = now.duration_since(self.last_update);\n\n        if elapsed >= self.debounce_duration {\n            // Execute immediately\n            self.entity.update(cx, update_fn);\n            self.last_update = now;\n            self.pending_update = None;\n        } else {\n            // Store for later\n            self.pending_update = Some(Box::new(update_fn));\n\n            // Schedule execution\n            let entity = self.entity.clone();\n            let delay = self.debounce_duration - elapsed;\n\n            cx.spawn(async move |cx| {\n                tokio::time::sleep(delay).await;\n\n                if let Some(update) = self.pending_update.take() {\n                    entity.update(cx, |state, inner_cx| {\n                        update(state, inner_cx);\n                    });\n                }\n            }).detach();\n        }\n    }\n}\n```\n\n### Throttled Updates\n\n```rust\nstruct ThrottledEntity<T> {\n    entity: Entity<T>,\n    last_update: Instant,\n    throttle_duration: Duration,\n}\n\nimpl<T: 'static> ThrottledEntity<T> {\n    fn new(entity: Entity<T>, throttle_ms: u64) -> Self {\n        Self {\n            entity,\n            last_update: Instant::now(),\n            throttle_duration: Duration::from_millis(throttle_ms),\n        }\n    }\n\n    fn try_update<F>(&mut self, cx: &mut App, update_fn: F) -> bool\n    where\n        F: FnOnce(&mut T, &mut Context<T>),\n    {\n        let now = Instant::now();\n        let elapsed = now.duration_since(self.last_update);\n\n        if elapsed >= self.throttle_duration {\n            self.entity.update(cx, update_fn);\n            self.last_update = now;\n            true\n        } else {\n            false // Update throttled\n        }\n    }\n}\n```\n\n## Entity State Machine Pattern\n\n```rust\nenum AppState {\n    Idle,\n    Loading,\n    Loaded(String),\n    Error(String),\n}\n\nstruct StateMachine {\n    state: AppState,\n}\n\nimpl StateMachine {\n    fn new() -> Self {\n        Self {\n            state: AppState::Idle,\n        }\n    }\n\n    fn start_loading(&mut self, cx: &mut Context<Self>) {\n        if matches!(self.state, AppState::Idle | AppState::Error(_)) {\n            self.state = AppState::Loading;\n            cx.notify();\n\n            let weak_entity = cx.entity().downgrade();\n\n            cx.spawn(async move |cx| {\n                let result = perform_load().await;\n\n                let _ = weak_entity.update(cx, |state, cx| {\n                    match result {\n                        Ok(data) => state.on_load_success(data, cx),\n                        Err(e) => state.on_load_error(e.to_string(), cx),\n                    }\n                });\n            }).detach();\n        }\n    }\n\n    fn on_load_success(&mut self, data: String, cx: &mut Context<Self>) {\n        if matches!(self.state, AppState::Loading) {\n            self.state = AppState::Loaded(data);\n            cx.notify();\n        }\n    }\n\n    fn on_load_error(&mut self, error: String, cx: &mut Context<Self>) {\n        if matches!(self.state, AppState::Loading) {\n            self.state = AppState::Error(error);\n            cx.notify();\n        }\n    }\n\n    fn reset(&mut self, cx: &mut Context<Self>) {\n        self.state = AppState::Idle;\n        cx.notify();\n    }\n}\n\nasync fn perform_load() -> Result<String, anyhow::Error> {\n    // Actual load implementation\n    Ok(\"Data\".to_string())\n}\n```\n\n## Entity Proxy Pattern\n\n```rust\nstruct EntityProxy<T> {\n    entity: WeakEntity<T>,\n}\n\nimpl<T> EntityProxy<T> {\n    fn new(entity: &Entity<T>) -> Self {\n        Self {\n            entity: entity.downgrade(),\n        }\n    }\n\n    fn with<F, R>(&self, cx: &mut App, f: F) -> Result<R, anyhow::Error>\n    where\n        F: FnOnce(&T, &App) -> R,\n    {\n        self.entity.read_with(cx, f)\n    }\n\n    fn update<F, R>(&self, cx: &mut App, f: F) -> Result<R, anyhow::Error>\n    where\n        F: FnOnce(&mut T, &mut Context<T>) -> R,\n    {\n        self.entity.update(cx, f)\n    }\n\n    fn is_valid(&self, cx: &App) -> bool {\n        self.entity.upgrade().is_some()\n    }\n}\n```\n\n## Cascading Updates Pattern\n\n```rust\nstruct CascadingUpdater {\n    entities: Vec<WeakEntity<UpdateTarget>>,\n}\n\nimpl CascadingUpdater {\n    fn new() -> Self {\n        Self {\n            entities: Vec::new(),\n        }\n    }\n\n    fn add_target(&mut self, entity: &Entity<UpdateTarget>) {\n        self.entities.push(entity.downgrade());\n    }\n\n    fn cascade_update<F>(&mut self, cx: &mut App, update_fn: F)\n    where\n        F: Fn(&mut UpdateTarget, &mut Context<UpdateTarget>) + Clone,\n    {\n        // Update all entities in sequence\n        self.entities.retain(|weak| {\n            if let Ok(_) = weak.update(cx, |state, inner_cx| {\n                update_fn.clone()(state, inner_cx);\n            }) {\n                true // Keep valid entity\n            } else {\n                false // Remove invalid entity\n            }\n        });\n    }\n}\n\nstruct UpdateTarget {\n    value: i32,\n}\n```\n\n## Entity Snapshot Pattern\n\n```rust\nuse serde::{Serialize, Deserialize};\n\n#[derive(Clone, Serialize, Deserialize)]\nstruct EntitySnapshot {\n    data: String,\n    timestamp: u64,\n}\n\nstruct SnapshotableEntity {\n    data: String,\n    snapshots: Vec<EntitySnapshot>,\n}\n\nimpl SnapshotableEntity {\n    fn new(data: String) -> Self {\n        Self {\n            data,\n            snapshots: Vec::new(),\n        }\n    }\n\n    fn take_snapshot(&mut self, cx: &mut Context<Self>) {\n        let snapshot = EntitySnapshot {\n            data: self.data.clone(),\n            timestamp: current_timestamp(),\n        };\n        self.snapshots.push(snapshot);\n        cx.notify();\n    }\n\n    fn restore_snapshot(&mut self, index: usize, cx: &mut Context<Self>) -> Result<(), String> {\n        if let Some(snapshot) = self.snapshots.get(index) {\n            self.data = snapshot.data.clone();\n            cx.notify();\n            Ok(())\n        } else {\n            Err(\"Invalid snapshot index\".to_string())\n        }\n    }\n\n    fn clear_old_snapshots(&mut self, keep_last: usize, cx: &mut Context<Self>) {\n        if self.snapshots.len() > keep_last {\n            self.snapshots.drain(0..self.snapshots.len() - keep_last);\n            cx.notify();\n        }\n    }\n}\n\nfn current_timestamp() -> u64 {\n    use std::time::{SystemTime, UNIX_EPOCH};\n    SystemTime::now()\n        .duration_since(UNIX_EPOCH)\n        .unwrap()\n        .as_secs()\n}\n```\n\n## Entity Transaction Pattern\n\n```rust\nstruct Transaction<T> {\n    entity: Entity<T>,\n    original_state: Option<T>,\n}\n\nimpl<T: Clone> Transaction<T> {\n    fn begin(entity: Entity<T>, cx: &mut App) -> Self {\n        let original_state = entity.read(cx).clone();\n\n        Self {\n            entity,\n            original_state: Some(original_state),\n        }\n    }\n\n    fn update<F>(&mut self, cx: &mut App, update_fn: F)\n    where\n        F: FnOnce(&mut T, &mut Context<T>),\n    {\n        self.entity.update(cx, update_fn);\n    }\n\n    fn commit(mut self, cx: &mut App) {\n        self.original_state = None; // Don't rollback\n        self.entity.update(cx, |_, cx| {\n            cx.notify();\n        });\n    }\n\n    fn rollback(mut self, cx: &mut App) {\n        if let Some(original) = self.original_state.take() {\n            self.entity.update(cx, |state, cx| {\n                *state = original;\n                cx.notify();\n            });\n        }\n    }\n}\n\nimpl<T> Drop for Transaction<T> {\n    fn drop(&mut self) {\n        // Auto-rollback if not committed\n        if self.original_state.is_some() {\n            eprintln!(\"Warning: Transaction dropped without commit\");\n        }\n    }\n}\n\n// Usage\nfn perform_transaction(entity: Entity<MyState>, cx: &mut App) -> Result<(), String> {\n    let mut tx = Transaction::begin(entity, cx);\n\n    tx.update(cx, |state, cx| {\n        state.value = 42;\n    });\n\n    if validate_state(&tx.entity, cx)? {\n        tx.commit(cx);\n        Ok(())\n    } else {\n        tx.rollback(cx);\n        Err(\"Validation failed\".to_string())\n    }\n}\n```\n\n## Entity Pool Pattern\n\n```rust\nstruct EntityPool<T> {\n    available: Vec<Entity<T>>,\n    in_use: Vec<WeakEntity<T>>,\n    factory: Box<dyn Fn(&mut App) -> Entity<T>>,\n}\n\nimpl<T: 'static> EntityPool<T> {\n    fn new<F>(factory: F) -> Self\n    where\n        F: Fn(&mut App) -> Entity<T> + 'static,\n    {\n        Self {\n            available: Vec::new(),\n            in_use: Vec::new(),\n            factory: Box::new(factory),\n        }\n    }\n\n    fn acquire(&mut self, cx: &mut App) -> Entity<T> {\n        let entity = if let Some(entity) = self.available.pop() {\n            entity\n        } else {\n            (self.factory)(cx)\n        };\n\n        self.in_use.push(entity.downgrade());\n        entity\n    }\n\n    fn release(&mut self, entity: Entity<T>, cx: &mut App) {\n        // Reset entity state if needed\n        entity.update(cx, |state, cx| {\n            // Reset logic here\n            cx.notify();\n        });\n\n        self.available.push(entity);\n        self.cleanup_in_use();\n    }\n\n    fn cleanup_in_use(&mut self) {\n        self.in_use.retain(|weak| weak.upgrade().is_some());\n    }\n\n    fn pool_size(&self) -> (usize, usize) {\n        (self.available.len(), self.in_use.len())\n    }\n}\n```\n\nThese advanced patterns provide powerful abstractions for managing complex entity scenarios while maintaining code quality and performance.\n"
  },
  {
    "path": ".claude/skills/gpui-entity/references/api-reference.md",
    "content": "# Entity API Reference\n\nComplete API documentation for GPUI's entity system.\n\n## Entity Types\n\n### Entity<T>\n\nA strong reference to state of type `T`.\n\n**Methods:**\n- `entity_id()` → `EntityId` - Returns unique identifier\n- `downgrade()` → `WeakEntity<T>` - Creates weak reference\n- `read(cx)` → `&T` - Immutable access to state\n- `read_with(cx, |state, cx| ...)` → `R` - Read with closure, returns closure result\n- `update(cx, |state, cx| ...)` → `R` - Mutable update with `Context<T>`, returns closure result\n- `update_in(cx, |state, window, cx| ...)` → `R` - Update with `Window` access (requires `AsyncWindowContext` or `VisualTestContext`)\n\n**Important Notes:**\n- Trying to update an entity while it's already being updated will panic\n- Within closures, use the inner `cx` provided to avoid multiple borrow issues\n- With async contexts, return values are wrapped in `anyhow::Result`\n\n### WeakEntity<T>\n\nA weak reference to state of type `T`.\n\n**Methods:**\n- `upgrade()` → `Option<Entity<T>>` - Convert to strong reference if still alive\n- `read_with(cx, |state, cx| ...)` → `Result<R>` - Read if entity exists\n- `update(cx, |state, cx| ...)` → `Result<R>` - Update if entity exists\n- `update_in(cx, |state, window, cx| ...)` → `Result<R>` - Update with window if entity exists\n\n**Use Cases:**\n- Avoid circular dependencies between entities\n- Store references in closures/callbacks without preventing cleanup\n- Optional relationships between components\n\n**Important:** All operations return `Result` since the entity may no longer exist.\n\n### AnyEntity\n\nDynamically-typed entity handle for storing entities of different types.\n\n### AnyWeakEntity\n\nDynamically-typed weak entity handle.\n\n## Entity Creation\n\n### cx.new()\n\nCreate new entity with initial state.\n\n```rust\nlet entity = cx.new(|cx| MyState {\n    count: 0,\n    name: \"Default\".to_string(),\n});\n```\n\n**Parameters:**\n- `cx: &mut App` or other context type\n- Closure receiving `&mut Context<T>` returning initial state `T`\n\n**Returns:** `Entity<T>`\n\n## Entity Operations\n\n### Reading State\n\n#### read()\n\nDirect read-only access to state.\n\n```rust\nlet count = my_entity.read(cx).count;\n```\n\n**Use when:** Simple field access, no context operations needed.\n\n#### read_with()\n\nRead with context access in closure.\n\n```rust\nlet count = my_entity.read_with(cx, |state, cx| {\n    // Can access both state and context\n    state.count\n});\n\n// Return multiple values\nlet (count, theme) = my_entity.read_with(cx, |state, cx| {\n    (state.count, cx.theme().clone())\n});\n```\n\n**Use when:** Need context operations, multiple return values, complex logic.\n\n### Updating State\n\n#### update()\n\nMutable update with `Context<T>`.\n\n```rust\nmy_entity.update(cx, |state, cx| {\n    state.count += 1;\n    cx.notify(); // Trigger re-render\n});\n```\n\n**Available Operations:**\n- `cx.notify()` - Trigger re-render\n- `cx.entity()` - Get current entity\n- `cx.emit(event)` - Emit event\n- `cx.spawn(task)` - Spawn async task\n- Other `Context<T>` methods\n\n#### update_in()\n\nUpdate with both `Window` and `Context<T>` access.\n\n```rust\nmy_entity.update_in(cx, |state, window, cx| {\n    state.focused = window.is_window_focused();\n    cx.notify();\n});\n```\n\n**Requires:** `AsyncWindowContext` or `VisualTestContext`\n\n**Use when:** Need window-specific operations like focus state, window bounds, etc.\n\n## Context Methods for Entities\n\n### cx.entity()\n\nGet current entity being updated.\n\n```rust\nimpl MyComponent {\n    fn some_method(&mut self, cx: &mut Context<Self>) {\n        let current_entity = cx.entity();  // Entity<MyComponent>\n        let weak = current_entity.downgrade();\n    }\n}\n```\n\n### cx.observe()\n\nObserve entity for changes.\n\n```rust\ncx.observe(&entity, |this, observed_entity, cx| {\n    // Called when observed_entity.update() calls cx.notify()\n    println!(\"Entity changed\");\n}).detach();\n```\n\n**Returns:** `Subscription` - Call `.detach()` to make permanent\n\n### cx.subscribe()\n\nSubscribe to events from entity.\n\n```rust\ncx.subscribe(&entity, |this, emitter, event: &SomeEvent, cx| {\n    // Called when emitter emits SomeEvent\n    match event {\n        SomeEvent::DataChanged => {\n            cx.notify();\n        }\n    }\n}).detach();\n```\n\n**Returns:** `Subscription` - Call `.detach()` to make permanent\n\n### cx.observe_new_entities()\n\nRegister callback for new entities of a type.\n\n```rust\ncx.observe_new_entities::<MyState>(|entity, cx| {\n    println!(\"New entity created: {:?}\", entity.entity_id());\n}).detach();\n```\n\n## Async Operations\n\n### cx.spawn()\n\nSpawn foreground task (UI thread).\n\n```rust\ncx.spawn(async move |this, cx| {\n    // `this`: WeakEntity<T>\n    // `cx`: &mut AsyncApp\n\n    let result = some_async_work().await;\n\n    // Update entity safely\n    let _ = this.update(cx, |state, cx| {\n        state.data = result;\n        cx.notify();\n    });\n}).detach();\n```\n\n**Note:** Always use weak entity reference in spawned tasks to prevent retain cycles.\n\n### cx.background_spawn()\n\nSpawn background task (background thread).\n\n```rust\ncx.background_spawn(async move {\n    // Long-running computation\n    let result = heavy_computation().await;\n    // Cannot directly update entities here\n    // Use channels or spawn foreground task to update\n}).detach();\n```\n\n## Entity Lifecycle\n\n### Creation\n\nEntities are created via `cx.new()` and immediately registered in the app.\n\n### Reference Counting\n\n- `Entity<T>` is a strong reference (increases reference count)\n- `WeakEntity<T>` is a weak reference (does not increase reference count)\n- Cloning `Entity<T>` increases reference count\n\n### Disposal\n\nEntities are automatically disposed when all strong references are dropped.\n\n```rust\n{\n    let entity = cx.new(|cx| MyState::default());\n    // entity exists\n} // entity dropped here if no other strong references exist\n```\n\n**Memory Leak Prevention:**\n- Use `WeakEntity` in closures/callbacks\n- Use `WeakEntity` for parent-child relationships\n- Avoid circular strong references\n\n## EntityId\n\nEvery entity has a unique identifier.\n\n```rust\nlet id: EntityId = entity.entity_id();\n\n// EntityIds can be compared\nif entity1.entity_id() == entity2.entity_id() {\n    // Same entity\n}\n```\n\n**Use Cases:**\n- Debugging and logging\n- Entity comparison without borrowing\n- Hash maps keyed by entity\n\n## Error Handling\n\n### WeakEntity Operations\n\nAll `WeakEntity` operations return `Result`:\n\n```rust\nlet weak = entity.downgrade();\n\n// Handle potential failure\nmatch weak.read_with(cx, |state, cx| state.count) {\n    Ok(count) => println!(\"Count: {}\", count),\n    Err(e) => eprintln!(\"Entity no longer exists: {}\", e),\n}\n\n// Or use Result combinators\nlet _ = weak.update(cx, |state, cx| {\n    state.count += 1;\n    cx.notify();\n}).ok(); // Ignore errors\n```\n\n### Update Panics\n\nNested updates on the same entity will panic:\n\n```rust\n// ❌ Will panic\nentity.update(cx, |state1, cx| {\n    entity.update(cx, |state2, cx| {\n        // Panic: entity already borrowed\n    });\n});\n```\n\n**Solution:** Perform updates sequentially or use different entities.\n\n## Type Conversions\n\n### Entity → WeakEntity\n\n```rust\nlet entity: Entity<T> = cx.new(|cx| T::default());\nlet weak: WeakEntity<T> = entity.downgrade();\n```\n\n### WeakEntity → Entity\n\n```rust\nlet weak: WeakEntity<T> = entity.downgrade();\nlet strong: Option<Entity<T>> = weak.upgrade();\n```\n\n### AnyEntity\n\n```rust\nlet any: AnyEntity = entity.into();\nlet typed: Option<Entity<T>> = any.downcast::<T>();\n```\n\n## Best Practice Guidelines\n\n### Always Use Inner cx\n\n```rust\n// ✅ Good: Use inner cx\nentity.update(cx, |state, inner_cx| {\n    inner_cx.notify(); // Use inner_cx, not outer cx\n});\n\n// ❌ Bad: Use outer cx\nentity.update(cx, |state, inner_cx| {\n    cx.notify(); // Wrong! Multiple borrow error\n});\n```\n\n### Weak References in Closures\n\n```rust\n// ✅ Good: Weak reference\nlet weak = cx.entity().downgrade();\ncallback(move || {\n    let _ = weak.update(cx, |state, cx| {\n        cx.notify();\n    });\n});\n\n// ❌ Bad: Strong reference (retain cycle)\nlet strong = cx.entity();\ncallback(move || {\n    strong.update(cx, |state, cx| {\n        // May never be dropped\n        cx.notify();\n    });\n});\n```\n\n### Sequential Updates\n\n```rust\n// ✅ Good: Sequential updates\nentity1.update(cx, |state, cx| { /* ... */ });\nentity2.update(cx, |state, cx| { /* ... */ });\n\n// ❌ Bad: Nested updates\nentity1.update(cx, |_, cx| {\n    entity2.update(cx, |_, cx| {\n        // May panic if entities are related\n    });\n});\n```\n"
  },
  {
    "path": ".claude/skills/gpui-entity/references/best-practices.md",
    "content": "# Entity Best Practices\n\nGuidelines and best practices for effective entity management in GPUI.\n\n## Avoiding Common Pitfalls\n\n### Avoid Entity Borrowing Conflicts\n\n**Problem:** Nested updates can cause borrow checker panics.\n\n```rust\n// ❌ Bad: Nested updates can panic\nentity1.update(cx, |_, cx| {\n    entity2.update(cx, |_, cx| {\n        // This may panic if entities are related\n    });\n});\n```\n\n**Solution:** Perform updates sequentially.\n\n```rust\n// ✅ Good: Sequential updates\nentity1.update(cx, |state1, cx| {\n    // Update entity1\n    state1.value = 42;\n    cx.notify();\n});\n\nentity2.update(cx, |state2, cx| {\n    // Update entity2\n    state2.value = 100;\n    cx.notify();\n});\n```\n\n### Use Weak References in Closures\n\n**Problem:** Strong references in closures can create retain cycles and memory leaks.\n\n```rust\n// ❌ Bad: Strong reference creates retain cycle\nimpl MyComponent {\n    fn setup_callback(&mut self, cx: &mut Context<Self>) {\n        let entity = cx.entity(); // Strong reference\n\n        some_callback(move || {\n            entity.update(cx, |state, cx| {\n                // This closure holds a strong reference\n                // If the closure itself is retained by the entity, memory leak!\n                cx.notify();\n            });\n        });\n    }\n}\n```\n\n**Solution:** Use weak references in closures.\n\n```rust\n// ✅ Good: Weak reference prevents retain cycle\nimpl MyComponent {\n    fn setup_callback(&mut self, cx: &mut Context<Self>) {\n        let weak_entity = cx.entity().downgrade(); // Weak reference\n\n        some_callback(move || {\n            // Safe: weak reference doesn't prevent cleanup\n            let _ = weak_entity.update(cx, |state, cx| {\n                cx.notify();\n            });\n        });\n    }\n}\n```\n\n### Use Inner Context in Closures\n\n**Problem:** Using outer context causes multiple borrow errors.\n\n```rust\n// ❌ Bad: Using outer cx causes borrow issues\nentity.update(cx, |state, inner_cx| {\n    cx.notify(); // Wrong! Using outer cx\n    cx.spawn(...); // Multiple borrow error\n});\n```\n\n**Solution:** Always use the inner context provided to the closure.\n\n```rust\n// ✅ Good: Use inner cx\nentity.update(cx, |state, inner_cx| {\n    inner_cx.notify(); // Correct\n    inner_cx.spawn(...); // Works fine\n});\n```\n\n### Entity as Props - Use Weak References\n\n**Problem:** Strong entity references in props can create ownership issues.\n\n```rust\n// ❌ Questionable: Strong reference in child\nstruct ChildComponent {\n    parent: Entity<ParentComponent>, // Strong reference\n}\n```\n\n**Better:** Use weak references for parent relationships.\n\n```rust\n// ✅ Good: Weak reference prevents issues\nstruct ChildComponent {\n    parent: WeakEntity<ParentComponent>, // Weak reference\n}\n\nimpl ChildComponent {\n    fn notify_parent(&mut self, cx: &mut Context<Self>) {\n        // Check if parent still exists\n        if let Ok(_) = self.parent.update(cx, |parent_state, cx| {\n            // Update parent\n            cx.notify();\n        }) {\n            // Parent successfully updated\n        }\n    }\n}\n```\n\n## Performance Optimization\n\n### Minimize cx.notify() Calls\n\nEach `cx.notify()` triggers a re-render. Batch updates when possible.\n\n```rust\n// ❌ Bad: Multiple notifications\nimpl MyComponent {\n    fn update_multiple_fields(&mut self, cx: &mut Context<Self>) {\n        self.field1 = new_value1;\n        cx.notify(); // Unnecessary intermediate notification\n\n        self.field2 = new_value2;\n        cx.notify(); // Unnecessary intermediate notification\n\n        self.field3 = new_value3;\n        cx.notify();\n    }\n}\n```\n\n```rust\n// ✅ Good: Single notification after all updates\nimpl MyComponent {\n    fn update_multiple_fields(&mut self, cx: &mut Context<Self>) {\n        self.field1 = new_value1;\n        self.field2 = new_value2;\n        self.field3 = new_value3;\n        cx.notify(); // Single notification\n    }\n}\n```\n\n### Conditional Updates\n\nOnly notify when state actually changes.\n\n```rust\nimpl MyComponent {\n    fn set_value(&mut self, new_value: i32, cx: &mut Context<Self>) {\n        if self.value != new_value {\n            self.value = new_value;\n            cx.notify(); // Only notify if changed\n        }\n    }\n}\n```\n\n### Use read_with for Complex Operations\n\nPrefer `read_with` over separate `read` calls.\n\n```rust\n// ❌ Less efficient: Multiple borrows\nlet state_ref = entity.read(cx);\nlet value1 = state_ref.field1;\nlet value2 = state_ref.field2;\n// state_ref borrowed for entire scope\n\n// ✅ More efficient: Single borrow with closure\nlet (value1, value2) = entity.read_with(cx, |state, cx| {\n    (state.field1, state.field2)\n});\n```\n\n### Avoid Excessive Entity Creation\n\nCreating entities has overhead. Reuse when appropriate.\n\n```rust\n// ❌ Bad: Creating entity per item in render\nimpl Render for MyList {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div().children(\n            self.items.iter().map(|item| {\n                // Don't create entities in render!\n                let entity = cx.new(|_| item.clone());\n                ItemView { entity }\n            })\n        )\n    }\n}\n```\n\n```rust\n// ✅ Good: Create entities once, reuse\nstruct MyList {\n    item_entities: Vec<Entity<Item>>,\n}\n\nimpl MyList {\n    fn add_item(&mut self, item: Item, cx: &mut Context<Self>) {\n        let entity = cx.new(|_| item);\n        self.item_entities.push(entity);\n        cx.notify();\n    }\n}\n```\n\n## Entity Lifecycle Management\n\n### Clean Up Weak References\n\nPeriodically clean up invalid weak references from collections.\n\n```rust\nstruct Container {\n    weak_children: Vec<WeakEntity<Child>>,\n}\n\nimpl Container {\n    fn cleanup_invalid_children(&mut self, cx: &mut Context<Self>) {\n        // Remove weak references that are no longer valid\n        let before_count = self.weak_children.len();\n        self.weak_children.retain(|weak| weak.upgrade().is_some());\n        let after_count = self.weak_children.len();\n\n        if before_count != after_count {\n            cx.notify(); // Notify if list changed\n        }\n    }\n}\n```\n\n### Entity Cloning and Sharing\n\nUnderstand that cloning `Entity<T>` increases reference count.\n\n```rust\n// Each clone increases the reference count\nlet entity1: Entity<MyState> = cx.new(|_| MyState::default());\nlet entity2 = entity1.clone(); // Reference count: 2\nlet entity3 = entity1.clone(); // Reference count: 3\n\n// Entity is dropped only when all references are dropped\ndrop(entity1); // Reference count: 2\ndrop(entity2); // Reference count: 1\ndrop(entity3); // Reference count: 0, entity is deallocated\n```\n\n### Proper Resource Cleanup\n\nImplement cleanup in `Drop` or explicit cleanup methods.\n\n```rust\nstruct ManagedResource {\n    handle: Option<FileHandle>,\n}\n\nimpl ManagedResource {\n    fn close(&mut self, cx: &mut Context<Self>) {\n        if let Some(handle) = self.handle.take() {\n            // Explicit cleanup\n            handle.close();\n            cx.notify();\n        }\n    }\n}\n\nimpl Drop for ManagedResource {\n    fn drop(&mut self) {\n        // Automatic cleanup when entity is dropped\n        if let Some(handle) = self.handle.take() {\n            handle.close();\n        }\n    }\n}\n```\n\n## Entity Observation Best Practices\n\n### Detach Subscriptions Appropriately\n\nCall `.detach()` on subscriptions you want to keep alive.\n\n```rust\nimpl MyComponent {\n    fn new(other_entity: Entity<OtherComponent>, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| {\n            // Observer will live as long as both entities exist\n            cx.observe(&other_entity, |this, observed, cx| {\n                // Handle changes\n                cx.notify();\n            }).detach(); // Important: detach to make permanent\n\n            Self { /* fields */ }\n        })\n    }\n}\n```\n\n### Avoid Observation Cycles\n\nDon't create mutual observation between entities.\n\n```rust\n// ❌ Bad: Mutual observation can cause infinite loops\nentity1.update(cx, |_, cx| {\n    cx.observe(&entity2, |_, _, cx| {\n        cx.notify(); // May trigger entity2's observer\n    }).detach();\n});\n\nentity2.update(cx, |_, cx| {\n    cx.observe(&entity1, |_, _, cx| {\n        cx.notify(); // May trigger entity1's observer → infinite loop\n    }).detach();\n});\n```\n\n## Async Best Practices\n\n### Always Use Weak References in Async Tasks\n\n```rust\n// ✅ Good: Weak reference in spawned task\nimpl MyComponent {\n    fn fetch_data(&mut self, cx: &mut Context<Self>) {\n        let weak_entity = cx.entity().downgrade();\n\n        cx.spawn(async move |cx| {\n            let data = fetch_from_api().await;\n\n            // Entity may have been dropped during fetch\n            let _ = weak_entity.update(cx, |state, cx| {\n                state.data = Some(data);\n                cx.notify();\n            });\n        }).detach();\n    }\n}\n```\n\n### Handle Async Errors Gracefully\n\n```rust\nimpl MyComponent {\n    fn fetch_data(&mut self, cx: &mut Context<Self>) {\n        let weak_entity = cx.entity().downgrade();\n\n        cx.spawn(async move |cx| {\n            match fetch_from_api().await {\n                Ok(data) => {\n                    let _ = weak_entity.update(cx, |state, cx| {\n                        state.data = Some(data);\n                        state.error = None;\n                        cx.notify();\n                    });\n                }\n                Err(e) => {\n                    let _ = weak_entity.update(cx, |state, cx| {\n                        state.error = Some(e.to_string());\n                        cx.notify();\n                    });\n                }\n            }\n        }).detach();\n    }\n}\n```\n\n### Cancellation Patterns\n\nImplement cancellation for long-running tasks.\n\n```rust\nstruct DataFetcher {\n    current_task: Option<Task<()>>,\n    data: Option<String>,\n}\n\nimpl DataFetcher {\n    fn fetch_data(&mut self, url: String, cx: &mut Context<Self>) {\n        // Cancel previous task\n        self.current_task = None; // Dropping task cancels it\n\n        let weak_entity = cx.entity().downgrade();\n\n        let task = cx.spawn(async move |cx| {\n            let data = fetch_from_url(&url).await?;\n\n            let _ = weak_entity.update(cx, |state, cx| {\n                state.data = Some(data);\n                cx.notify();\n            });\n\n            Ok::<(), anyhow::Error>(())\n        });\n\n        self.current_task = Some(task);\n    }\n}\n```\n\n## Testing Best Practices\n\n### Use TestAppContext for Entity Tests\n\n```rust\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use gpui::TestAppContext;\n\n    #[gpui::test]\n    fn test_entity_update(cx: &mut TestAppContext) {\n        let entity = cx.new(|_| MyState { count: 0 });\n\n        entity.update(cx, |state, cx| {\n            state.count += 1;\n            assert_eq!(state.count, 1);\n        });\n\n        let count = entity.read(cx).count;\n        assert_eq!(count, 1);\n    }\n}\n```\n\n### Test Entity Observation\n\n```rust\n#[gpui::test]\nfn test_entity_observation(cx: &mut TestAppContext) {\n    let observed = cx.new(|_| MyState { value: 0 });\n    let observer = cx.new(|cx| Observer::new(observed.clone(), cx));\n\n    // Update observed entity\n    observed.update(cx, |state, cx| {\n        state.value = 42;\n        cx.notify();\n    });\n\n    // Verify observer was notified\n    observer.read(cx).assert_observed();\n}\n```\n\n## Performance Checklist\n\nBefore shipping entity-based code, verify:\n\n- [ ] No strong references in closures/callbacks (use `WeakEntity`)\n- [ ] No nested entity updates (use sequential updates)\n- [ ] Using inner `cx` in update closures\n- [ ] Batching updates before calling `cx.notify()`\n- [ ] Cleaning up invalid weak references periodically\n- [ ] Using `read_with` for complex read operations\n- [ ] Properly detaching subscriptions and observers\n- [ ] Using weak references in async tasks\n- [ ] No observation cycles between entities\n- [ ] Proper error handling in async operations\n- [ ] Resource cleanup in `Drop` or explicit methods\n- [ ] Tests cover entity lifecycle and interactions\n"
  },
  {
    "path": ".claude/skills/gpui-entity/references/patterns.md",
    "content": "# Entity Patterns\n\nCommon patterns and use cases for entity management in GPUI.\n\n## Application Scenarios\n\n### Model-View Separation\n\nSeparate business logic (model) from UI (view) using entities.\n\n```rust\nstruct CounterModel {\n    count: usize,\n    listeners: Vec<Box<dyn Fn(usize)>>,\n}\n\nstruct CounterView {\n    model: Entity<CounterModel>,\n}\n\nimpl CounterModel {\n    fn increment(&mut self, cx: &mut Context<Self>) {\n        self.count += 1;\n\n        // Notify listeners\n        for listener in &self.listeners {\n            listener(self.count);\n        }\n\n        cx.notify();\n    }\n\n    fn decrement(&mut self, cx: &mut Context<Self>) {\n        if self.count > 0 {\n            self.count -= 1;\n            cx.notify();\n        }\n    }\n}\n\nimpl CounterView {\n    fn new(cx: &mut App) -> Entity<Self> {\n        let model = cx.new(|_cx| CounterModel {\n            count: 0,\n            listeners: Vec::new(),\n        });\n\n        cx.new(|cx| Self { model })\n    }\n\n    fn increment_count(&mut self, cx: &mut Context<Self>) {\n        self.model.update(cx, |model, cx| {\n            model.increment(cx);\n        });\n    }\n}\n\nimpl Render for CounterView {\n    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let count = self.model.read(cx).count;\n\n        div()\n            .child(format!(\"Count: {}\", count))\n            .child(\n                Button::new(\"increment\")\n                    .label(\"Increment\")\n                    .on_click(cx.listener(|this, _, cx| {\n                        this.increment_count(cx);\n                    }))\n            )\n    }\n}\n```\n\n### Component State Management\n\nManaging complex component state with entities.\n\n```rust\nstruct TodoList {\n    todos: Vec<Todo>,\n    filter: TodoFilter,\n    next_id: usize,\n}\n\nstruct Todo {\n    id: usize,\n    text: String,\n    completed: bool,\n}\n\nenum TodoFilter {\n    All,\n    Active,\n    Completed,\n}\n\nimpl TodoList {\n    fn new() -> Self {\n        Self {\n            todos: Vec::new(),\n            filter: TodoFilter::All,\n            next_id: 0,\n        }\n    }\n\n    fn add_todo(&mut self, text: String, cx: &mut Context<Self>) {\n        self.todos.push(Todo {\n            id: self.next_id,\n            text,\n            completed: false,\n        });\n        self.next_id += 1;\n        cx.notify();\n    }\n\n    fn toggle_todo(&mut self, id: usize, cx: &mut Context<Self>) {\n        if let Some(todo) = self.todos.iter_mut().find(|t| t.id == id) {\n            todo.completed = !todo.completed;\n            cx.notify();\n        }\n    }\n\n    fn remove_todo(&mut self, id: usize, cx: &mut Context<Self>) {\n        self.todos.retain(|t| t.id != id);\n        cx.notify();\n    }\n\n    fn set_filter(&mut self, filter: TodoFilter, cx: &mut Context<Self>) {\n        self.filter = filter;\n        cx.notify();\n    }\n\n    fn visible_todos(&self) -> impl Iterator<Item = &Todo> {\n        self.todos.iter().filter(move |todo| match self.filter {\n            TodoFilter::All => true,\n            TodoFilter::Active => !todo.completed,\n            TodoFilter::Completed => todo.completed,\n        })\n    }\n}\n```\n\n### Cross-Entity Communication\n\nCoordinating state between parent and child entities.\n\n```rust\nstruct ParentComponent {\n    child_entities: Vec<Entity<ChildComponent>>,\n    global_message: String,\n}\n\nstruct ChildComponent {\n    id: usize,\n    message: String,\n    parent: WeakEntity<ParentComponent>,\n}\n\nimpl ParentComponent {\n    fn new(cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self {\n            child_entities: Vec::new(),\n            global_message: String::new(),\n        })\n    }\n\n    fn add_child(&mut self, cx: &mut Context<Self>) {\n        let parent_weak = cx.entity().downgrade();\n        let child_id = self.child_entities.len();\n\n        let child = cx.new(|cx| ChildComponent {\n            id: child_id,\n            message: String::new(),\n            parent: parent_weak,\n        });\n\n        self.child_entities.push(child);\n        cx.notify();\n    }\n\n    fn broadcast_message(&mut self, message: String, cx: &mut Context<Self>) {\n        self.global_message = message.clone();\n\n        // Update all children\n        for child in &self.child_entities {\n            child.update(cx, |child_state, cx| {\n                child_state.message = message.clone();\n                cx.notify();\n            });\n        }\n\n        cx.notify();\n    }\n}\n\nimpl ChildComponent {\n    fn notify_parent(&mut self, message: String, cx: &mut Context<Self>) {\n        if let Ok(_) = self.parent.update(cx, |parent_state, cx| {\n            parent_state.global_message = format!(\"Child {}: {}\", self.id, message);\n            cx.notify();\n        }) {\n            // Parent successfully notified\n        }\n    }\n}\n```\n\n### Async Operations with Entities\n\nManaging async state updates.\n\n```rust\nstruct DataLoader {\n    loading: bool,\n    data: Option<String>,\n    error: Option<String>,\n}\n\nimpl DataLoader {\n    fn new() -> Self {\n        Self {\n            loading: false,\n            data: None,\n            error: None,\n        }\n    }\n\n    fn load_data(&mut self, cx: &mut Context<Self>) {\n        // Set loading state\n        self.loading = true;\n        self.error = None;\n        cx.notify();\n\n        // Get weak reference for async task\n        let entity = cx.entity().downgrade();\n\n        cx.spawn(async move |cx| {\n            // Simulate async operation\n            tokio::time::sleep(Duration::from_secs(2)).await;\n            let result = fetch_data().await;\n\n            // Update entity with result\n            let _ = entity.update(cx, |state, cx| {\n                state.loading = false;\n                match result {\n                    Ok(data) => state.data = Some(data),\n                    Err(e) => state.error = Some(e.to_string()),\n                }\n                cx.notify();\n            });\n        }).detach();\n    }\n}\n\nasync fn fetch_data() -> Result<String, anyhow::Error> {\n    // Actual fetch implementation\n    Ok(\"Fetched data\".to_string())\n}\n```\n\n### Background Task Coordination\n\nUsing background tasks with entity updates.\n\n```rust\nstruct ImageProcessor {\n    images: Vec<ProcessedImage>,\n    processing: bool,\n}\n\nstruct ProcessedImage {\n    path: PathBuf,\n    thumbnail: Option<Vec<u8>>,\n}\n\nimpl ImageProcessor {\n    fn process_images(&mut self, paths: Vec<PathBuf>, cx: &mut Context<Self>) {\n        self.processing = true;\n        cx.notify();\n\n        let entity = cx.entity().downgrade();\n\n        cx.background_spawn({\n            let paths = paths.clone();\n            async move {\n                let mut processed = Vec::new();\n\n                for path in paths {\n                    // Process image on background thread\n                    let thumbnail = generate_thumbnail(&path).await;\n                    processed.push((path, thumbnail));\n                }\n\n                // Send results back to foreground\n                processed\n            }\n        })\n        .then(cx.spawn(move |processed, cx| {\n            // Update entity on foreground thread\n            let _ = entity.update(cx, |state, cx| {\n                for (path, thumbnail) in processed {\n                    state.images.push(ProcessedImage {\n                        path,\n                        thumbnail: Some(thumbnail),\n                    });\n                }\n                state.processing = false;\n                cx.notify();\n            });\n        }))\n        .detach();\n    }\n}\n```\n\n## Common Patterns\n\n### 1. Stateful Components\n\nUse entities for components that maintain internal state.\n\n```rust\nstruct StatefulComponent {\n    value: i32,\n    history: Vec<i32>,\n}\n\nimpl StatefulComponent {\n    fn update_value(&mut self, new_value: i32, cx: &mut Context<Self>) {\n        self.history.push(self.value);\n        self.value = new_value;\n        cx.notify();\n    }\n\n    fn undo(&mut self, cx: &mut Context<Self>) {\n        if let Some(prev_value) = self.history.pop() {\n            self.value = prev_value;\n            cx.notify();\n        }\n    }\n}\n```\n\n### 2. Shared State\n\nShare state between multiple components using entities.\n\n```rust\nstruct SharedState {\n    theme: Theme,\n    user: Option<User>,\n}\n\nstruct ComponentA {\n    shared: Entity<SharedState>,\n}\n\nstruct ComponentB {\n    shared: Entity<SharedState>,\n}\n\n// Both components can read/update the same shared state\nimpl ComponentA {\n    fn update_theme(&mut self, theme: Theme, cx: &mut Context<Self>) {\n        self.shared.update(cx, |state, cx| {\n            state.theme = theme;\n            cx.notify();\n        });\n    }\n}\n```\n\n### 3. Event Coordination\n\nUse entities to coordinate events between components.\n\n```rust\nstruct EventCoordinator {\n    listeners: Vec<WeakEntity<dyn EventListener>>,\n}\n\ntrait EventListener {\n    fn on_event(&mut self, event: &AppEvent, cx: &mut App);\n}\n\nimpl EventCoordinator {\n    fn emit_event(&mut self, event: AppEvent, cx: &mut Context<Self>) {\n        // Notify all listeners\n        self.listeners.retain(|weak_listener| {\n            weak_listener.update(cx, |listener, cx| {\n                listener.on_event(&event, cx);\n            }).is_ok()\n        });\n        cx.notify();\n    }\n}\n```\n\n### 4. Async State Management\n\nManage state that changes based on async operations.\n\n```rust\nstruct AsyncState<T> {\n    state: AsyncValue<T>,\n}\n\nenum AsyncValue<T> {\n    Loading,\n    Loaded(T),\n    Error(String),\n}\n\nimpl<T> AsyncState<T> {\n    fn is_loading(&self) -> bool {\n        matches!(self.state, AsyncValue::Loading)\n    }\n\n    fn value(&self) -> Option<&T> {\n        match &self.state {\n            AsyncValue::Loaded(v) => Some(v),\n            _ => None,\n        }\n    }\n}\n```\n\n### 5. Parent-Child Relationships\n\nManage hierarchical relationships with weak references.\n\n```rust\nstruct Parent {\n    children: Vec<Entity<Child>>,\n}\n\nstruct Child {\n    parent: WeakEntity<Parent>,\n    data: String,\n}\n\nimpl Child {\n    fn notify_parent_of_change(&mut self, cx: &mut Context<Self>) {\n        if let Ok(_) = self.parent.update(cx, |parent, cx| {\n            // Parent can react to child change\n            cx.notify();\n        }) {\n            // Successfully notified\n        }\n    }\n}\n```\n\n### 6. Observer Pattern\n\nReact to entity state changes using observers.\n\n```rust\nstruct Observable {\n    value: i32,\n}\n\nstruct Observer {\n    observed: Entity<Observable>,\n}\n\nimpl Observer {\n    fn new(observed: Entity<Observable>, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| {\n            // Observe the entity\n            cx.observe(&observed, |this, observed_entity, cx| {\n                // React to changes\n                let value = observed_entity.read(cx).value;\n                println!(\"Value changed to: {}\", value);\n            }).detach();\n\n            Self { observed }\n        })\n    }\n}\n```\n\n### 7. Event Subscription\n\nHandle events emitted by other entities.\n\n```rust\n#[derive(Clone)]\nenum DataEvent {\n    Updated,\n    Deleted,\n}\n\nstruct DataSource {\n    data: Vec<String>,\n}\n\nimpl DataSource {\n    fn update_data(&mut self, cx: &mut Context<Self>) {\n        // Update data\n        cx.emit(DataEvent::Updated);\n        cx.notify();\n    }\n}\n\nstruct DataConsumer {\n    source: Entity<DataSource>,\n}\n\nimpl DataConsumer {\n    fn new(source: Entity<DataSource>, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| {\n            // Subscribe to events\n            cx.subscribe(&source, |this, source, event: &DataEvent, cx| {\n                match event {\n                    DataEvent::Updated => {\n                        // Handle update\n                        cx.notify();\n                    }\n                    DataEvent::Deleted => {\n                        // Handle deletion\n                    }\n                }\n            }).detach();\n\n            Self { source }\n        })\n    }\n}\n```\n\n### 8. Resource Management\n\nManage external resources with proper cleanup.\n\n```rust\nstruct FileHandle {\n    path: PathBuf,\n    file: Option<File>,\n}\n\nimpl FileHandle {\n    fn open(&mut self, cx: &mut Context<Self>) -> Result<()> {\n        self.file = Some(File::open(&self.path)?);\n        cx.notify();\n        Ok(())\n    }\n\n    fn close(&mut self, cx: &mut Context<Self>) {\n        self.file = None;\n        cx.notify();\n    }\n}\n\nimpl Drop for FileHandle {\n    fn drop(&mut self) {\n        // Cleanup when entity is dropped\n        if let Some(file) = self.file.take() {\n            drop(file);\n        }\n    }\n}\n```\n\n## Pattern Selection Guide\n\n| Need | Pattern | Complexity |\n|------|---------|------------|\n| Component with internal state | Stateful Components | Low |\n| State shared by multiple components | Shared State | Low |\n| Coordinate events between components | Event Coordination | Medium |\n| Handle async data fetching | Async State Management | Medium |\n| Parent-child component hierarchy | Parent-Child Relationships | Medium |\n| React to state changes | Observer Pattern | Medium |\n| Handle custom events | Event Subscription | Medium-High |\n| Manage external resources | Resource Management | High |\n\nChoose the simplest pattern that meets your requirements. Combine patterns as needed for complex scenarios.\n"
  },
  {
    "path": ".claude/skills/gpui-event/SKILL.md",
    "content": "---\nname: gpui-event\ndescription: Event handling and subscriptions in GPUI. Use when implementing events, observers, or event-driven patterns. Supports custom events, entity observations, and event subscriptions for coordinating between components.\n---\n\n## Overview\n\nGPUI provides event system for component coordination:\n\n**Event Mechanisms:**\n- **Custom Events**: Define and emit type-safe events\n- **Observations**: React to entity state changes\n- **Subscriptions**: Listen to events from other entities\n- **Global Events**: App-wide event handling\n\n## Quick Start\n\n### Define and Emit Events\n\n```rust\n#[derive(Clone)]\nenum MyEvent {\n    DataUpdated(String),\n    ActionTriggered,\n}\n\nimpl MyComponent {\n    fn update_data(&mut self, data: String, cx: &mut Context<Self>) {\n        self.data = data.clone();\n\n        // Emit event\n        cx.emit(MyEvent::DataUpdated(data));\n        cx.notify();\n    }\n}\n```\n\n### Subscribe to Events\n\n```rust\nimpl Listener {\n    fn new(source: Entity<MyComponent>, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| {\n            // Subscribe to events\n            cx.subscribe(&source, |this, emitter, event: &MyEvent, cx| {\n                match event {\n                    MyEvent::DataUpdated(data) => {\n                        this.handle_update(data.clone(), cx);\n                    }\n                    MyEvent::ActionTriggered => {\n                        this.handle_action(cx);\n                    }\n                }\n            }).detach();\n\n            Self { source }\n        })\n    }\n}\n```\n\n### Observe Entity Changes\n\n```rust\nimpl Observer {\n    fn new(target: Entity<Target>, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| {\n            // Observe entity for any changes\n            cx.observe(&target, |this, observed, cx| {\n                // Called when observed.update() calls cx.notify()\n                println!(\"Target changed\");\n                cx.notify();\n            }).detach();\n\n            Self { target }\n        })\n    }\n}\n```\n\n## Common Patterns\n\n### 1. Parent-Child Communication\n\n```rust\n// Parent emits events\nimpl Parent {\n    fn notify_children(&mut self, cx: &mut Context<Self>) {\n        cx.emit(ParentEvent::Updated);\n        cx.notify();\n    }\n}\n\n// Children subscribe\nimpl Child {\n    fn new(parent: Entity<Parent>, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| {\n            cx.subscribe(&parent, |this, parent, event, cx| {\n                this.handle_parent_event(event, cx);\n            }).detach();\n\n            Self { parent }\n        })\n    }\n}\n```\n\n### 2. Global Event Broadcasting\n\n```rust\nstruct EventBus {\n    listeners: Vec<WeakEntity<dyn Listener>>,\n}\n\nimpl EventBus {\n    fn broadcast(&mut self, event: GlobalEvent, cx: &mut Context<Self>) {\n        self.listeners.retain(|weak| {\n            weak.update(cx, |listener, cx| {\n                listener.on_event(&event, cx);\n            }).is_ok()\n        });\n    }\n}\n```\n\n### 3. Observer Pattern\n\n```rust\ncx.observe(&entity, |this, observed, cx| {\n    // React to any state change\n    let state = observed.read(cx);\n    this.sync_with_state(state, cx);\n}).detach();\n```\n\n## Best Practices\n\n### ✅ Detach Subscriptions\n\n```rust\n// ✅ Detach to keep alive\ncx.subscribe(&entity, |this, source, event, cx| {\n    // Handle event\n}).detach();\n```\n\n### ✅ Clean Event Types\n\n```rust\n#[derive(Clone)]\nenum AppEvent {\n    DataChanged { id: usize, value: String },\n    ActionPerformed(ActionType),\n    Error(String),\n}\n```\n\n### ❌ Avoid Event Loops\n\n```rust\n// ❌ Don't create mutual subscriptions\nentity1.subscribe(entity2) → emits event\nentity2.subscribe(entity1) → emits event → infinite loop!\n```\n\n## Reference Documentation\n\n- **API Reference**: See [api-reference.md](references/api-reference.md)\n  - Event definition, emission, subscriptions\n  - Observations, global events\n  - Subscription lifecycle\n\n- **Patterns**: See [patterns.md](references/patterns.md)\n  - Event-driven architectures\n  - Communication patterns\n  - Best practices and pitfalls\n"
  },
  {
    "path": ".claude/skills/gpui-focus-handle/SKILL.md",
    "content": "---\nname: gpui-focus-handle\ndescription: Focus management and keyboard navigation in GPUI. Use when handling focus, focus handles, or keyboard navigation. Enables keyboard-driven interfaces with proper focus tracking and navigation between focusable elements.\n---\n\n## Overview\n\nGPUI's focus system enables keyboard navigation and focus management.\n\n**Key Concepts:**\n- **FocusHandle**: Reference to focusable element\n- **Focus tracking**: Current focused element\n- **Keyboard navigation**: Tab/Shift-Tab between elements\n- **Focus events**: on_focus, on_blur\n\n## Quick Start\n\n### Creating Focus Handles\n\n```rust\nstruct FocusableComponent {\n    focus_handle: FocusHandle,\n}\n\nimpl FocusableComponent {\n    fn new(cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n}\n```\n\n### Making Elements Focusable\n\n```rust\nimpl Render for FocusableComponent {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .track_focus(&self.focus_handle)\n            .on_action(cx.listener(Self::on_enter))\n            .child(\"Focusable content\")\n    }\n\n    fn on_enter(&mut self, _: &Enter, cx: &mut Context<Self>) {\n        // Handle Enter key when focused\n        cx.notify();\n    }\n}\n```\n\n### Focus Management\n\n```rust\nimpl MyComponent {\n    fn focus(&mut self, cx: &mut Context<Self>) {\n        self.focus_handle.focus(cx);\n    }\n\n    fn is_focused(&self, cx: &App) -> bool {\n        self.focus_handle.is_focused(cx)\n    }\n\n    fn blur(&mut self, cx: &mut Context<Self>) {\n        cx.blur();\n    }\n}\n```\n\n## Focus Events\n\n### Handling Focus Changes\n\n```rust\nimpl Render for MyInput {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let is_focused = self.focus_handle.is_focused(cx);\n\n        div()\n            .track_focus(&self.focus_handle)\n            .on_focus(cx.listener(|this, _event, cx| {\n                this.on_focus(cx);\n            }))\n            .on_blur(cx.listener(|this, _event, cx| {\n                this.on_blur(cx);\n            }))\n            .when(is_focused, |el| {\n                el.bg(cx.theme().focused_background)\n            })\n            .child(self.render_content())\n    }\n}\n\nimpl MyInput {\n    fn on_focus(&mut self, cx: &mut Context<Self>) {\n        // Handle focus gained\n        cx.notify();\n    }\n\n    fn on_blur(&mut self, cx: &mut Context<Self>) {\n        // Handle focus lost\n        cx.notify();\n    }\n}\n```\n\n## Keyboard Navigation\n\n### Tab Order\n\nElements with `track_focus()` automatically participate in Tab navigation.\n\n```rust\ndiv()\n    .child(\n        input1.track_focus(&focus1)  // Tab order: 1\n    )\n    .child(\n        input2.track_focus(&focus2)  // Tab order: 2\n    )\n    .child(\n        input3.track_focus(&focus3)  // Tab order: 3\n    )\n```\n\n### Focus Within Containers\n\n```rust\nimpl Container {\n    fn focus_first(&mut self, cx: &mut Context<Self>) {\n        if let Some(first) = self.children.first() {\n            first.update(cx, |child, cx| {\n                child.focus_handle.focus(cx);\n            });\n        }\n    }\n\n    fn focus_next(&mut self, cx: &mut Context<Self>) {\n        // Custom focus navigation logic\n    }\n}\n```\n\n## Common Patterns\n\n### 1. Auto-focus on Mount\n\n```rust\nimpl MyDialog {\n    fn new(cx: &mut Context<Self>) -> Self {\n        let focus_handle = cx.focus_handle();\n\n        // Focus when created\n        focus_handle.focus(cx);\n\n        Self { focus_handle }\n    }\n}\n```\n\n### 2. Focus Trap (Modal)\n\n```rust\nimpl Modal {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .track_focus(&self.focus_handle)\n            .on_key_down(cx.listener(|this, event: &KeyDownEvent, cx| {\n                if event.key == Key::Tab {\n                    // Keep focus within modal\n                    this.focus_next_in_modal(cx);\n                    cx.stop_propagation();\n                }\n            }))\n            .child(self.render_content())\n    }\n}\n```\n\n### 3. Conditional Focus\n\n```rust\nimpl Searchable {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .track_focus(&self.focus_handle)\n            .when(self.search_active, |el| {\n                el.on_mount(cx.listener(|this, _, cx| {\n                    this.focus_handle.focus(cx);\n                }))\n            })\n            .child(self.search_input())\n    }\n}\n```\n\n## Best Practices\n\n### ✅ Track Focus on Interactive Elements\n\n```rust\n// ✅ Good: Track focus for keyboard interaction\ninput()\n    .track_focus(&self.focus_handle)\n    .on_action(cx.listener(Self::on_enter))\n```\n\n### ✅ Provide Visual Focus Indicators\n\n```rust\nlet is_focused = self.focus_handle.is_focused(cx);\n\ndiv()\n    .when(is_focused, |el| {\n        el.border_color(cx.theme().focused_border)\n    })\n```\n\n### ❌ Don't: Forget to Track Focus\n\n```rust\n// ❌ Bad: No track_focus, keyboard navigation won't work\ndiv()\n    .on_action(cx.listener(Self::on_enter))\n```\n\n## Reference Documentation\n\n- **API Reference**: See [api-reference.md](references/api-reference.md)\n  - FocusHandle API, focus management\n  - Events, keyboard navigation\n  - Best practices\n"
  },
  {
    "path": ".claude/skills/gpui-global/SKILL.md",
    "content": "---\nname: gpui-global\ndescription: Global state management in GPUI. Use when implementing global state, app-wide configuration, or shared resources.\n---\n\n## Overview\n\nGlobal state in GPUI provides app-wide shared data accessible from any context.\n\n**Key Trait**: `Global` - Implement on types to make them globally accessible\n\n## Quick Start\n\n### Define Global State\n\n```rust\nuse gpui::Global;\n\n#[derive(Clone)]\nstruct AppSettings {\n    theme: Theme,\n    language: String,\n}\n\nimpl Global for AppSettings {}\n```\n\n### Set and Access Globals\n\n```rust\nfn main() {\n    let app = Application::new();\n    app.run(|cx: &mut App| {\n        // Set global\n        cx.set_global(AppSettings {\n            theme: Theme::Dark,\n            language: \"en\".to_string(),\n        });\n\n        // Access global (read-only)\n        let settings = cx.global::<AppSettings>();\n        println!(\"Theme: {:?}\", settings.theme);\n    });\n}\n```\n\n### Update Globals\n\n```rust\nimpl MyComponent {\n    fn change_theme(&mut self, new_theme: Theme, cx: &mut Context<Self>) {\n        cx.update_global::<AppSettings, _>(|settings, cx| {\n            settings.theme = new_theme;\n            // Global updates don't trigger automatic notifications\n            // Manually notify components that care\n        });\n\n        cx.notify(); // Re-render this component\n    }\n}\n```\n\n## Common Use Cases\n\n### 1. App Configuration\n\n```rust\n#[derive(Clone)]\nstruct AppConfig {\n    api_endpoint: String,\n    max_retries: u32,\n    timeout: Duration,\n}\n\nimpl Global for AppConfig {}\n\n// Set once at startup\ncx.set_global(AppConfig {\n    api_endpoint: \"https://api.example.com\".to_string(),\n    max_retries: 3,\n    timeout: Duration::from_secs(30),\n});\n\n// Access anywhere\nlet config = cx.global::<AppConfig>();\n```\n\n### 2. Feature Flags\n\n```rust\n#[derive(Clone)]\nstruct FeatureFlags {\n    enable_beta_features: bool,\n    enable_analytics: bool,\n}\n\nimpl Global for FeatureFlags {}\n\nimpl MyComponent {\n    fn render_beta_feature(&self, cx: &App) -> Option<impl IntoElement> {\n        let flags = cx.global::<FeatureFlags>();\n\n        if flags.enable_beta_features {\n            Some(div().child(\"Beta feature\"))\n        } else {\n            None\n        }\n    }\n}\n```\n\n### 3. Shared Services\n\n```rust\n#[derive(Clone)]\nstruct ServiceRegistry {\n    http_client: Arc<HttpClient>,\n    logger: Arc<Logger>,\n}\n\nimpl Global for ServiceRegistry {}\n\nimpl MyComponent {\n    fn fetch_data(&mut self, cx: &mut Context<Self>) {\n        let registry = cx.global::<ServiceRegistry>();\n        let client = registry.http_client.clone();\n\n        cx.spawn(async move |cx| {\n            let data = client.get(\"api/data\").await?;\n            // Process data...\n            Ok::<_, anyhow::Error>(())\n        }).detach();\n    }\n}\n```\n\n## Best Practices\n\n### ✅ Use Arc for Shared Resources\n\n```rust\n#[derive(Clone)]\nstruct GlobalState {\n    database: Arc<Database>,  // Cheap to clone\n    cache: Arc<RwLock<Cache>>,\n}\n\nimpl Global for GlobalState {}\n```\n\n### ✅ Immutable by Default\n\nGlobals are read-only by default. Use interior mutability when needed:\n\n```rust\n#[derive(Clone)]\nstruct Counter {\n    count: Arc<AtomicUsize>,\n}\n\nimpl Global for Counter {}\n\nimpl Counter {\n    fn increment(&self) {\n        self.count.fetch_add(1, Ordering::SeqCst);\n    }\n\n    fn get(&self) -> usize {\n        self.count.load(Ordering::SeqCst)\n    }\n}\n```\n\n### ❌ Don't: Overuse Globals\n\n```rust\n// ❌ Bad: Too many globals\ncx.set_global(UserState { ... });\ncx.set_global(CartState { ... });\ncx.set_global(CheckoutState { ... });\n\n// ✅ Good: Use entities for component state\nlet user_entity = cx.new(|_| UserState { ... });\n```\n\n## When to Use\n\n**Use Globals for:**\n- App-wide configuration\n- Feature flags\n- Shared services (HTTP client, logger)\n- Read-only reference data\n\n**Use Entities for:**\n- Component-specific state\n- State that changes frequently\n- State that needs notifications\n\n## Reference Documentation\n\n- **API Reference**: See [api-reference.md](references/api-reference.md)\n  - Global trait, set_global, update_global\n  - Interior mutability patterns\n  - Best practices and anti-patterns\n"
  },
  {
    "path": ".claude/skills/gpui-layout-and-style/SKILL.md",
    "content": "---\nname: gpui-layout-and-style\ndescription: Layout and styling in GPUI. Use when styling components, layout systems, or CSS-like properties.\n---\n\n## Overview\n\nGPUI provides CSS-like styling with Rust type safety.\n\n**Key Concepts:**\n- Flexbox layout system\n- Styled trait for chaining styles\n- Size units: `px()`, `rems()`, `relative()`\n- Colors, borders, shadows\n\n## Quick Start\n\n### Basic Styling\n\n```rust\nuse gpui::*;\n\ndiv()\n    .w(px(200.))\n    .h(px(100.))\n    .bg(rgb(0x2196F3))\n    .text_color(rgb(0xFFFFFF))\n    .rounded(px(8.))\n    .p(px(16.))\n    .child(\"Styled content\")\n```\n\n### Flexbox Layout\n\n```rust\ndiv()\n    .flex()\n    .flex_row()  // or flex_col() for column\n    .gap(px(8.))\n    .items_center()\n    .justify_between()\n    .children([\n        div().child(\"Item 1\"),\n        div().child(\"Item 2\"),\n        div().child(\"Item 3\"),\n    ])\n```\n\n### Size Units\n\n```rust\ndiv()\n    .w(px(200.))           // Pixels\n    .h(rems(10.))          // Relative to font size\n    .w(relative(0.5))      // 50% of parent\n    .min_w(px(100.))\n    .max_w(px(400.))\n```\n\n## Common Patterns\n\n### Centered Content\n\n```rust\ndiv()\n    .flex()\n    .items_center()\n    .justify_center()\n    .size_full()\n    .child(\"Centered\")\n```\n\n### Card Layout\n\n```rust\ndiv()\n    .w(px(300.))\n    .bg(cx.theme().surface)\n    .rounded(px(8.))\n    .shadow_md()\n    .p(px(16.))\n    .gap(px(12.))\n    .flex()\n    .flex_col()\n    .child(heading())\n    .child(content())\n```\n\n### Responsive Spacing\n\n```rust\ndiv()\n    .p(px(16.))           // Padding all sides\n    .px(px(20.))          // Padding horizontal\n    .py(px(12.))          // Padding vertical\n    .pt(px(8.))           // Padding top\n    .gap(px(8.))          // Gap between children\n```\n\n## Styling Methods\n\n### Dimensions\n\n```rust\n.w(px(200.))              // Width\n.h(px(100.))              // Height\n.size(px(200.))           // Width and height\n.min_w(px(100.))          // Min width\n.max_w(px(400.))          // Max width\n```\n\n### Colors\n\n```rust\n.bg(rgb(0x2196F3))        // Background\n.text_color(rgb(0xFFFFFF)) // Text color\n.border_color(rgb(0x000000)) // Border color\n```\n\n### Borders\n\n```rust\n.border(px(1.))           // Border width\n.rounded(px(8.))          // Border radius\n.rounded_t(px(8.))        // Top corners\n.border_color(rgb(0x000000))\n```\n\n### Spacing\n\n```rust\n.p(px(16.))               // Padding\n.m(px(8.))                // Margin\n.gap(px(8.))              // Gap between flex children\n```\n\n### Flexbox\n\n```rust\n.flex()                   // Enable flexbox\n.flex_row()               // Row direction\n.flex_col()               // Column direction\n.items_center()           // Align items center\n.justify_between()        // Space between items\n.flex_grow()              // Grow to fill space\n```\n\n## Theme Integration\n\n```rust\ndiv()\n    .bg(cx.theme().surface)\n    .text_color(cx.theme().foreground)\n    .border_color(cx.theme().border)\n    .when(is_hovered, |el| {\n        el.bg(cx.theme().hover)\n    })\n```\n\n## Conditional Styling\n\n```rust\ndiv()\n    .when(is_active, |el| {\n        el.bg(cx.theme().primary)\n    })\n    .when_some(optional_color, |el, color| {\n        el.bg(color)\n    })\n```\n\n## Reference Documentation\n\n- **Complete Guide**: See [reference.md](references/reference.md)\n  - All styling methods\n  - Layout strategies\n  - Theming, responsive design\n"
  },
  {
    "path": ".claude/skills/gpui-style-guide/SKILL.md",
    "content": "---\nname: gpui-style-guide\ndescription: GPUI Component project style guide based on gpui-component code patterns. Use when writing new components, reviewing code, or ensuring consistency with existing gpui-component implementations. Covers component structure, trait implementations, naming conventions, and API patterns observed in the actual codebase.\n---\n\n## Overview\n\nCode style guide derived from gpui-component implementation patterns.\n\n**Based on**: Analysis of Button, Checkbox, Input, Select, and other components in crates/ui\n\n## Component Structure\n\n### Basic Component Pattern\n\n```rust\nuse gpui::{\n    div, prelude::FluentBuilder as _, AnyElement, App, Div, ElementId,\n    InteractiveElement, IntoElement, ParentElement, RenderOnce,\n    StatefulInteractiveElement, StyleRefinement, Styled, Window,\n};\n\n#[derive(IntoElement)]\npub struct MyComponent {\n    id: ElementId,\n    base: Div,\n    style: StyleRefinement,\n\n    // Configuration fields\n    size: Size,\n    disabled: bool,\n    selected: bool,\n\n    // Content fields\n    label: Option<Text>,\n    children: Vec<AnyElement>,\n\n    // Callbacks (use Rc for Clone)\n    on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,\n}\n\nimpl MyComponent {\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            base: div(),\n            style: StyleRefinement::default(),\n            size: Size::default(),\n            disabled: false,\n            selected: false,\n            label: None,\n            children: Vec::new(),\n            on_click: None,\n        }\n    }\n\n    // Builder methods\n    pub fn label(mut self, label: impl Into<Text>) -> Self {\n        self.label = Some(label.into());\n        self\n    }\n\n    pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {\n        self.on_click = Some(Rc::new(handler));\n        self\n    }\n}\n\nimpl InteractiveElement for MyComponent {\n    fn interactivity(&mut self) -> &mut gpui::Interactivity {\n        self.base.interactivity()\n    }\n}\n\nimpl StatefulInteractiveElement for MyComponent {}\n\nimpl Styled for MyComponent {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for MyComponent {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        // Implementation\n        self.base\n    }\n}\n```\n\n### Stateful Component Pattern\n\n```rust\n#[derive(IntoElement)]\npub struct Select {\n    state: Entity<SelectState>,\n    style: StyleRefinement,\n    size: Size,\n    // ...\n}\n\npub struct SelectState {\n    open: bool,\n    selected_index: Option<usize>,\n    // ...\n}\n\nimpl Select {\n    pub fn new(state: &Entity<SelectState>) -> Self {\n        Self {\n            state: state.clone(),\n            size: Size::default(),\n            style: StyleRefinement::default(),\n        }\n    }\n}\n```\n\n## Trait Implementations\n\n### Sizable\n\n```rust\nimpl Sizable for MyComponent {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n```\n\n### Selectable\n\n```rust\nimpl Selectable for MyComponent {\n    fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        self.selected\n    }\n}\n```\n\n### Disableable\n\n```rust\nimpl Disableable for MyComponent {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    fn is_disabled(&self) -> bool {\n        self.disabled\n    }\n}\n```\n\n## Variant Patterns\n\n### Enum Variants\n\n```rust\n#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]\npub enum ButtonVariant {\n    Primary,\n    #[default]\n    Secondary,\n    Danger,\n    Success,\n    Warning,\n    Ghost,\n    Link,\n}\n```\n\n### Trait-Based Variant API\n\n```rust\npub trait ButtonVariants: Sized {\n    fn with_variant(self, variant: ButtonVariant) -> Self;\n\n    /// With the primary style for the Button.\n    fn primary(self) -> Self {\n        self.with_variant(ButtonVariant::Primary)\n    }\n\n    /// With the danger style for the Button.\n    fn danger(self) -> Self {\n        self.with_variant(ButtonVariant::Danger)\n    }\n\n    // ... more variants\n}\n```\n\n### Custom Variant Pattern\n\n```rust\n#[derive(Clone, Copy, PartialEq, Eq, Debug)]\npub struct ButtonCustomVariant {\n    color: Hsla,\n    foreground: Hsla,\n    border: Hsla,\n    hover: Hsla,\n    active: Hsla,\n    shadow: bool,\n}\n\nimpl ButtonCustomVariant {\n    pub fn new(cx: &App) -> Self {\n        Self {\n            color: cx.theme().transparent,\n            foreground: cx.theme().foreground,\n            // ...\n            shadow: false,\n        }\n    }\n\n    pub fn color(mut self, color: Hsla) -> Self {\n        self.color = color;\n        self\n    }\n\n    // ... more builder methods\n}\n```\n\n## Action and Keybinding Patterns\n\n### Context Constant\n\n```rust\nconst CONTEXT: &str = \"Select\";\n```\n\n### Init Function\n\n```rust\npub(crate) fn init(cx: &mut App) {\n    cx.bind_keys([\n        KeyBinding::new(\"up\", SelectUp, Some(CONTEXT)),\n        KeyBinding::new(\"down\", SelectDown, Some(CONTEXT)),\n        KeyBinding::new(\"enter\", Confirm { secondary: false }, Some(CONTEXT)),\n        KeyBinding::new(\"escape\", Cancel, Some(CONTEXT)),\n    ])\n}\n```\n\n### Action Usage\n\n```rust\nuse crate::actions::{Cancel, Confirm, SelectDown, SelectUp};\n\ndiv()\n    .key_context(CONTEXT)\n    .on_action(cx.listener(Self::on_action_select_up))\n    .on_action(cx.listener(Self::on_action_confirm))\n```\n\n## Trait Definitions\n\n### Item Traits\n\n```rust\npub trait SelectItem: Clone {\n    type Value: Clone;\n\n    fn title(&self) -> SharedString;\n\n    fn display_title(&self) -> Option<AnyElement> {\n        None\n    }\n\n    fn render(&self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        self.title().into_element()\n    }\n\n    fn value(&self) -> &Self::Value;\n\n    fn matches(&self, query: &str) -> bool {\n        self.title().to_lowercase().contains(&query.to_lowercase())\n    }\n}\n```\n\n### Implement for Common Types\n\n```rust\nimpl SelectItem for String {\n    type Value = Self;\n\n    fn title(&self) -> SharedString {\n        SharedString::from(self.to_string())\n    }\n\n    fn value(&self) -> &Self::Value {\n        &self\n    }\n}\n\nimpl SelectItem for SharedString { /* ... */ }\nimpl SelectItem for &'static str { /* ... */ }\n```\n\n## Icon Pattern\n\n### IconNamed Trait\n\n```rust\npub trait IconNamed {\n    fn path(self) -> SharedString;\n}\n\nimpl<T: IconNamed> From<T> for Icon {\n    fn from(value: T) -> Self {\n        Icon::build(value)\n    }\n}\n```\n\n### IconName Enum\n\n```rust\n#[derive(IntoElement, Clone)]\npub enum IconName {\n    ArrowDown,\n    ArrowUp,\n    Check,\n    Close,\n    // ... all icon names\n}\n```\n\n## Documentation Patterns\n\n### Component Documentation\n\n```rust\n/// A Checkbox element.\n#[derive(IntoElement)]\npub struct Checkbox { }\n```\n\n### Method Documentation\n\n```rust\nimpl Checkbox {\n    /// Create a new Checkbox with the given id.\n    pub fn new(id: impl Into<ElementId>) -> Self { }\n\n    /// Set the label for the checkbox.\n    pub fn label(mut self, label: impl Into<Text>) -> Self { }\n\n    /// Set the click handler for the checkbox.\n    ///\n    /// The `&bool` parameter indicates the new checked state after the click.\n    pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self { }\n}\n```\n\n## Import Organization Pattern\n\n```rust\n// 1. External crate imports\nuse std::rc::Rc;\n\n// 2. Crate imports\nuse crate::{\n    ActiveTheme, Disableable, FocusableExt, Icon, IconName,\n    Selectable, Sizable, Size, StyledExt,\n};\n\n// 3. GPUI imports\nuse gpui::{\n    div, prelude::FluentBuilder as _, px, relative, rems,\n    AnyElement, App, Div, ElementId, InteractiveElement,\n    IntoElement, ParentElement, RenderOnce,\n    StatefulInteractiveElement, StyleRefinement, Styled, Window,\n};\n```\n\n## Field Organization\n\n```rust\npub struct Component {\n    // 1. Identity\n    id: ElementId,\n    base: Div,\n    style: StyleRefinement,\n\n    // 2. Configuration\n    size: Size,\n    disabled: bool,\n    selected: bool,\n    tab_stop: bool,\n    tab_index: isize,\n\n    // 3. Content/children\n    label: Option<Text>,\n    children: Vec<AnyElement>,\n    prefix: Option<AnyElement>,\n    suffix: Option<AnyElement>,\n\n    // 4. Callbacks (last)\n    on_click: Option<Rc<dyn Fn(Args, &mut Window, &mut App) + 'static>>,\n}\n```\n\n## Common Patterns\n\n### Optional Elements\n\n```rust\npub fn prefix(mut self, prefix: impl IntoElement) -> Self {\n    self.prefix = Some(prefix.into_any_element());\n    self\n}\n```\n\n### Callback Patterns\n\n```rust\n// Pattern 1: Event parameter first\npub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {\n    self.on_click = Some(Rc::new(handler));\n    self\n}\n\n// Pattern 2: State parameter\npub fn on_change(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {\n    self.on_change = Some(Rc::new(handler));\n    self\n}\n```\n\n### Static Handler Functions\n\n```rust\nfn handle_click(\n    on_click: &Option<Rc<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,\n    checked: bool,\n    window: &mut Window,\n    cx: &mut App,\n) {\n    let new_checked = !checked;\n    if let Some(f) = on_click {\n        (f)(&new_checked, window, cx);\n    }\n}\n```\n\n### Boolean Methods\n\n```rust\n// Enable/disable patterns\npub fn cleanable(mut self, cleanable: bool) -> Self {\n    self.cleanable = cleanable;\n    self\n}\n\n// Toggle methods (no parameter)\npub fn mask_toggle(mut self) -> Self {\n    self.mask_toggle = true;\n    self\n}\n```\n\n## Size Methods\n\n### Size Trait\n\n```rust\nimpl Sizable for Component {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n```\n\n### Convenience Size Methods (from StyleSized trait)\n\nComponents get `.xsmall()`, `.small()`, `.medium()`, `.large()` automatically via `StyleSized` trait.\n\n## Rendering Patterns\n\n### RenderOnce Pattern\n\n```rust\nimpl RenderOnce for MyComponent {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let (width, height) = self.size.input_size();\n\n        self.base\n            .id(self.id)\n            .flex()\n            .items_center()\n            .gap(px(8.))\n            .min_w(width)\n            .h(height)\n            .when(self.disabled, |this| {\n                this.opacity(0.5).cursor_not_allowed()\n            })\n            .children(self.children)\n    }\n}\n```\n\n## Theme Usage\n\n```rust\n// Access theme colors\ncx.theme().surface\ncx.theme().foreground\ncx.theme().border\ncx.theme().primary\ncx.theme().transparent\n\n// Use in components\ndiv()\n    .bg(cx.theme().surface)\n    .text_color(cx.theme().foreground)\n    .border_color(cx.theme().border)\n```\n\n## Reference Documentation\n\n- **Component Examples**: See [component-examples.md](references/component-examples.md)\n  - Full component implementations\n  - Common patterns in action\n\n- **Trait Patterns**: See [trait-patterns.md](references/trait-patterns.md)\n  - Detailed trait implementation guides\n  - Custom trait design patterns\n\n## Quick Checklist\n\nWhen creating a new component in crates/ui:\n\n- [ ] `#[derive(IntoElement)]` on struct\n- [ ] Include `id: ElementId`, `base: Div`, `style: StyleRefinement`\n- [ ] Implement `InteractiveElement`, `StatefulInteractiveElement`, `Styled`\n- [ ] Implement `RenderOnce` trait\n- [ ] Implement `Sizable` if component has sizes\n- [ ] Implement `Selectable` if component can be selected\n- [ ] Implement `Disableable` if component can be disabled\n- [ ] Use `Rc<dyn Fn>` for callbacks\n- [ ] Use `Option<AnyElement>` for optional child elements\n- [ ] Import `prelude::FluentBuilder as _`\n- [ ] Use theme colors via `cx.theme()`\n- [ ] Follow field organization pattern\n"
  },
  {
    "path": ".claude/skills/gpui-test/SKILL.md",
    "content": "---\nname: gpui-test\ndescription: Writing tests for GPUI applications. Use when testing components, async operations, or UI behavior.\n---\n\n## Overview\n\nGPUI provides a comprehensive testing framework that allows you to test UI components, async operations, and distributed systems. Tests run on a single-threaded executor that provides deterministic execution and the ability to test complex async scenarios. GPUI tests use the `#[gpui::test]` attribute and work with `TestAppContext` for basic testing and `VisualTestContext` for window-dependent tests.\n\n### Rules\n\n- If test does not require windows or rendering, we can avoid use `[gpui::test]` and `TestAppContext`, just write simple rust test.\n\n## Core Testing Infrastructure\n\n### Test Attributes\n\n#### Basic Test\n\n```rust\n#[gpui::test]\nfn my_test(cx: &mut TestAppContext) {\n    // Test implementation\n}\n```\n\n#### Async Test\n\n```rust\n#[gpui::test]\nasync fn my_async_test(cx: &mut TestAppContext) {\n    // Async test implementation\n}\n```\n\n#### Property Test with Iterations\n\n```rust\n#[gpui::test(iterations = 10)]\nfn my_property_test(cx: &mut TestAppContext, mut rng: StdRng) {\n    // Property testing with random data\n}\n```\n\n### Test Contexts\n\n#### TestAppContext\n\n`TestAppContext` provides access to GPUI's core functionality without windows:\n\n```rust\n#[gpui::test]\nfn test_entity_operations(cx: &mut TestAppContext) {\n    // Create entities\n    let entity = cx.new(|cx| MyComponent::new(cx));\n\n    // Update entities\n    entity.update(cx, |component, cx| {\n        component.value = 42;\n        cx.notify();\n    });\n\n    // Read entities\n    let value = entity.read_with(cx, |component, _| component.value);\n    assert_eq!(value, 42);\n}\n```\n\n#### VisualTestContext\n\n`VisualTestContext` extends `TestAppContext` with window support:\n\n```rust\n#[gpui::test]\nfn test_with_window(cx: &mut TestAppContext) {\n    // Create window with component\n    let window = cx.update(|cx| {\n        cx.open_window(Default::default(), |_, cx| {\n            cx.new(|cx| MyComponent::new(cx))\n        }).unwrap()\n    });\n\n    // Convert to visual context\n    let mut cx = VisualTestContext::from_window(window.into(), cx);\n\n    // Access window and component\n    let component = window.root(&mut cx).unwrap();\n}\n```\n\n## Additional Resources\n\n- For detailed testing patterns and examples, see [reference.md](reference.md)\n- For best practices and running tests, see [examples.md](examples.md)\n"
  },
  {
    "path": ".claude/skills/gpui-test/examples.md",
    "content": "## Testing Best Practices\n\n### Test Organization\n\nGroup related tests in modules:\n\n```rust\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    mod entity_tests {\n        use super::*;\n\n        #[gpui::test]\n        fn test_creation() { /* ... */ }\n\n        #[gpui::test]\n        fn test_updates() { /* ... */ }\n    }\n\n    mod async_tests {\n        use super::*;\n\n        #[gpui::test]\n        async fn test_async_ops() { /* ... */ }\n    }\n\n    mod distributed_tests {\n        use super::*;\n\n        #[gpui::test]\n        fn test_multi_app() { /* ... */ }\n    }\n}\n```\n\n### Setup and Teardown\n\nUse helper functions for common setup:\n\n```rust\nfn create_test_counter(cx: &mut TestAppContext) -> Entity<Counter> {\n    cx.new(|cx| Counter::new(cx))\n}\n\n#[gpui::test]\nfn test_counter_operations(cx: &mut TestAppContext) {\n    let counter = create_test_counter(cx);\n\n    // Test operations\n}\n```\n\n### Assertions\n\nUse descriptive assertions:\n\n```rust\n#[gpui::test]\nfn test_counter_bounds(cx: &mut TestAppContext) {\n    let counter = create_test_counter(cx);\n\n    // Test upper bound\n    for _ in 0..100 {\n        counter.update(cx, |counter, cx| {\n            counter.increment(cx);\n        });\n    }\n\n    let count = counter.read_with(cx, |counter, _| counter.count);\n    assert!(count <= 100, \"Counter should not exceed maximum\");\n\n    // Test lower bound\n    for _ in 0..200 {\n        counter.update(cx, |counter, cx| {\n            counter.decrement(cx);\n        });\n    }\n\n    let count = counter.read_with(cx, |counter, _| counter.count);\n    assert!(count >= 0, \"Counter should not go below minimum\");\n}\n```\n\n### Performance Testing\n\nTest performance characteristics:\n\n```rust\n#[gpui::test]\nfn test_operation_performance(cx: &mut TestAppContext) {\n    let component = cx.new(|cx| MyComponent::new(cx));\n\n    let start = std::time::Instant::now();\n\n    // Perform many operations\n    for i in 0..1000 {\n        component.update(cx, |comp, cx| {\n            comp.perform_operation(i, cx);\n        });\n    }\n\n    let elapsed = start.elapsed();\n    assert!(elapsed < Duration::from_millis(100), \"Operations should complete quickly\");\n}\n```\n\n## Running Tests\n\n### Basic Test Execution\n\n```bash\n# Run all tests\ncargo test\n\n# Run specific test\ncargo test test_counter_operations\n\n# Run tests in a specific module\ncargo test entity_tests::\n\n# Run with output\ncargo test -- --nocapture\n```\n\n### Test Configuration\n\nEnable test-support feature for GPUI tests:\n\n```toml\n[features]\ntest-support = [\"gpui/test-support\"]\n```\n\n```bash\ncargo test --features test-support\n```\n\n### Advanced Test Execution\n\n```bash\n# Run tests with iterations for property testing\ncargo test -- --test-threads=1\n\n# Run tests matching a pattern\ncargo test test_async\n\n# Run tests with backtrace on failure\nRUST_BACKTRACE=1 cargo test\n```\n\n### CI/CD Integration\n\nFor continuous integration:\n\n```yaml\n# .github/workflows/test.yml\nname: Tests\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: dtolnay/rust-toolchain@stable\n      - name: Run tests\n        run: cargo test --features test-support\n```\n\nGPUI's testing framework provides deterministic, fast, and comprehensive testing capabilities that mirror real application behavior while providing the control needed for thorough testing of complex UI and async scenarios."
  },
  {
    "path": ".claude/skills/gpui-test/reference.md",
    "content": "## Testing Patterns\n\n### Basic Entity Testing\n\nTest entity creation, updates, and reads:\n\n```rust\n#[gpui::test]\nfn test_counter_entity(cx: &mut TestAppContext) {\n    let counter = cx.new(|cx| Counter::new(cx));\n\n    // Test initial state\n    let initial_count = counter.read_with(cx, |counter, _| counter.count);\n    assert_eq!(initial_count, 0);\n\n    // Test updates\n    counter.update(cx, |counter, cx| {\n        counter.count = 42;\n        cx.notify();\n    });\n\n    let updated_count = counter.read_with(cx, |counter, _| counter.count);\n    assert_eq!(updated_count, 42);\n}\n```\n\n### Event Testing\n\nTest event emission and handling:\n\n```rust\n#[derive(Clone)]\nstruct ValueChanged {\n    new_value: i32,\n}\n\nimpl EventEmitter<ValueChanged> for MyComponent {}\n\n#[gpui::test]\nfn test_event_emission(cx: &mut TestAppContext) {\n    let component = cx.new(|cx| {\n        let mut comp = MyComponent::default();\n\n        // Subscribe to self\n        cx.subscribe_self(|this, event: &ValueChanged, cx| {\n            this.received_value = event.new_value;\n            cx.notify();\n        });\n\n        comp\n    });\n\n    // Emit event\n    component.update(cx, |_, cx| {\n        cx.emit(ValueChanged { new_value: 123 });\n    });\n\n    // Verify event was handled\n    let received = component.read_with(cx, |comp, _| comp.received_value);\n    assert_eq!(received, 123);\n}\n```\n\n### Action Testing\n\nTest action dispatching and handling:\n\n```rust\nactions!(my_app, [Increment, Decrement]);\n\n#[gpui::test]\nfn test_action_dispatch(cx: &mut TestAppContext) {\n    let window = cx.update(|cx| {\n        cx.open_window(Default::default(), |_, cx| {\n            cx.new(|cx| MyComponent::new(cx))\n        }).unwrap()\n    });\n\n    let mut cx = VisualTestContext::from_window(window.into(), cx);\n    let counter = window.root(&mut cx).unwrap();\n\n    // Dispatch action via focus handle\n    let focus_handle = counter.read_with(&cx, |counter, _| counter.focus_handle.clone());\n    cx.update(|window, cx| {\n        focus_handle.dispatch_action(&Increment, window, cx);\n    });\n\n    let count = counter.read_with(&cx, |counter, _| counter.count);\n    assert_eq!(count, 1);\n}\n```\n\n### Async Testing\n\nTest async operations and background tasks:\n\n```rust\nimpl MyComponent {\n    fn load_data(&self, cx: &mut Context<Self>) -> Task<i32> {\n        cx.spawn(async move |this, cx| {\n            // Simulate async work\n            this.update(cx, |comp, _| comp.loading = true).await;\n            // Return result\n            42\n        })\n    }\n\n    fn background_update(&self, cx: &mut Context<Self>) {\n        cx.spawn(async move |this, cx| {\n            // Background work\n            this.update(cx, |comp, _| {\n                comp.value += 10;\n            }).await;\n        }).detach();\n    }\n}\n\n#[gpui::test]\nasync fn test_async_operations(cx: &mut TestAppContext) {\n    let component = cx.new(|cx| MyComponent::new(cx));\n\n    // Test awaited task\n    let result = component.update(cx, |comp, cx| comp.load_data(cx)).await;\n    assert_eq!(result, 42);\n\n    // Test detached task\n    component.update(cx, |comp, cx| comp.background_update(cx));\n\n    // Detached tasks don't run until you yield\n    let value_before = component.read_with(cx, |comp, _| comp.value);\n    assert_eq!(value_before, 0);\n\n    // Run pending tasks\n    cx.run_until_parked();\n\n    let value_after = component.read_with(cx, |comp, _| comp.value);\n    assert_eq!(value_after, 10);\n}\n```\n\n### Timer Testing\n\nTest timer-based operations:\n\n```rust\nimpl MyComponent {\n    fn delayed_action(&self, cx: &mut Context<Self>) {\n        cx.spawn(async move |this, cx| {\n            cx.background_executor()\n                .timer(Duration::from_millis(100))\n                .await;\n\n            this.update(cx, |comp, cx| {\n                comp.action_performed = true;\n                cx.notify();\n            }).await;\n        }).detach();\n    }\n}\n\n#[gpui::test]\nasync fn test_timers(cx: &mut TestAppContext) {\n    let component = cx.new(|cx| MyComponent::new(cx));\n\n    component.update(cx, |comp, cx| comp.delayed_action(cx));\n\n    // Action shouldn't have completed yet\n    let performed = component.read_with(cx, |comp, _| comp.action_performed);\n    assert!(!performed);\n\n    // Run until parked (timers complete)\n    cx.run_until_parked();\n\n    let performed = component.read_with(cx, |comp, _| comp.action_performed);\n    assert!(performed);\n}\n```\n\n### External I/O Testing\n\nFor tests involving external systems, use `allow_parking()`:\n\n```rust\n#[gpui::test]\nasync fn test_external_io(cx: &mut TestAppContext) {\n    // Allow parking for external I/O\n    cx.executor().allow_parking();\n\n    // Simulate external operation\n    let (tx, rx) = futures::channel::oneshot::channel();\n    std::thread::spawn(move || {\n        std::thread::sleep(Duration::from_millis(10));\n        tx.send(42).ok();\n    });\n\n    let result = rx.await.unwrap();\n    assert_eq!(result, 42);\n}\n```\n\n## Property Testing\n\nUse random data to test edge cases:\n\n```rust\n#[gpui::test(iterations = 10)]\nfn test_counter_random_operations(cx: &mut TestAppContext, mut rng: StdRng) {\n    let counter = cx.new(|cx| Counter::new(cx));\n\n    let mut expected = 0i32;\n    for _ in 0..100 {\n        let delta = rng.random_range(-10..=10);\n        expected += delta;\n\n        counter.update(cx, |counter, cx| {\n            counter.count += delta;\n            cx.notify();\n        });\n    }\n\n    let actual = counter.read_with(cx, |counter, _| counter.count);\n    assert_eq!(actual, expected);\n}\n```\n\n## Distributed Systems Testing\n\nTest multiple app contexts communicating:\n\n```rust\n#[derive(Clone)]\nstruct NetworkMessage {\n    from: String,\n    to: String,\n    data: i32,\n}\n\n#[gpui::test]\nfn test_distributed_apps(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {\n    // Create components in different app contexts\n    let comp_a = cx_a.new(|_| MyComponent::new(\"A\".to_string()));\n    let comp_b = cx_b.new(|_| MyComponent::new(\"B\".to_string()));\n\n    // Simulate message passing\n    comp_a.update(cx_a, |comp, cx| {\n        comp.send_message(\"B\", 42, cx);\n    });\n\n    // Run async operations\n    cx_a.run_until_parked();\n\n    // Verify message received in other context\n    comp_b.update(cx_b, |comp, _| {\n        comp.receive_messages();\n    });\n\n    let messages = comp_b.read_with(cx_b, |comp, _| comp.messages.clone());\n    assert_eq!(messages.len(), 1);\n    assert_eq!(messages[0].data, 42);\n}\n```\n\n### Interleaving Testing\n\nTest concurrent operations with random execution order:\n\n```rust\n#[gpui::test(iterations = 10)]\nfn test_concurrent_operations(\n    cx_a: &mut TestAppContext,\n    cx_b: &mut TestAppContext,\n    mut rng: StdRng,\n) {\n    let comp_a = cx_a.new(|_| MyComponent::new());\n    let comp_b = cx_b.new(|_| MyComponent::new());\n\n    // Perform random operations across contexts\n    for i in 0..20 {\n        if rng.random_bool(0.5) {\n            comp_a.update(cx_a, |comp, cx| {\n                comp.perform_operation(i, cx);\n            });\n        } else {\n            comp_b.update(cx_b, |comp, cx| {\n                comp.perform_operation(i, cx);\n            });\n        }\n    }\n\n    // Run all pending operations\n    cx_a.run_until_parked();\n\n    // Verify final state\n    let state_a = comp_a.read_with(cx_a, |comp, _| comp.state);\n    let state_b = comp_b.read_with(cx_b, |comp, _| comp.state);\n\n    // Assert invariants hold despite execution order\n    assert!(state_a.is_consistent());\n    assert!(state_b.is_consistent());\n}\n```\n\n## Mocking and Isolation\n\n### Network Mocking\n\nCreate mock networks for testing distributed features:\n\n```rust\nstruct MockNetwork {\n    messages: Arc<Mutex<Vec<NetworkMessage>>>,\n}\n\nimpl MockNetwork {\n    fn new() -> Self {\n        Self {\n            messages: Arc::new(Mutex::new(Vec::new())),\n        }\n    }\n\n    fn send(&self, message: NetworkMessage) {\n        self.messages.lock().unwrap().push(message);\n    }\n\n    fn receive_all(&self) -> Vec<NetworkMessage> {\n        self.messages.lock().unwrap().drain(..).collect()\n    }\n}\n\n#[gpui::test]\nfn test_networked_components(cx: &mut TestAppContext) {\n    let network = Arc::new(MockNetwork::new());\n\n    let sender = cx.new(|_| MessageSender::new(network.clone()));\n    let receiver = cx.new(|_| MessageReceiver::new(network));\n\n    // Send message\n    sender.update(cx, |sender, _| {\n        sender.send(\"Hello\");\n    });\n\n    // Receive message\n    receiver.update(cx, |receiver, _| {\n        receiver.receive_all();\n    });\n\n    let received = receiver.read_with(cx, |receiver, _| receiver.messages.clone());\n    assert_eq!(received, vec![\"Hello\"]);\n}\n```"
  },
  {
    "path": ".claude/skills/new-component/SKILL.md",
    "content": "---\nname: new-component\ndescription: Create new GPUI components. Use when building components, writing UI elements, or creating new component implementations.\n---\n\n## Instructions\n\nWhen creating new GPUI components:\n\n1. **Follow existing patterns**: Base implementation on components in `crates/ui/src` (examples: `Button`, `Select`, `Dialog`)\n2. **Style consistency**: Follow existing component styles and Shadcn UI patterns\n3. **Component type decision**:\n   - Use stateless elements for simple components (like `Button`)\n   - Use stateful elements for complex components with data (like `Select` and `SelectState`)\n   - Use composition for components built on existing components (like `AlertDialog` based on `Dialog`)\n4. **API consistency**: Maintain the same API style as other elements\n5. **Documentation**: Create component documentation\n6. **Stories**: Write component stories in the story folder\n7. **Registration**: Add the component to `crates/story/src/main.rs` story list\n\n## Component Types\n\n- **Stateless**: Pure presentation components without internal state (e.g., `Button`)\n- **Stateful**: Components that manage their own state and data (e.g., `Select`)\n- **Composite**: Components built on top of existing components (e.g., `AlertDialog` based on `Dialog`)\n\n## Implementation Steps\n\n### 1. Create Component File\n\nCreate a new file in `crates/ui/src/` (e.g., `alert_dialog.rs`):\n\n```rust\nuse gpui::{App, ClickEvent, Pixels, SharedString, Window, px};\nuse std::rc::Rc;\n\npub struct AlertDialog {\n    pub(crate) variant: AlertVariant,\n    pub(crate) title: SharedString,\n    // ... other fields\n}\n\nimpl AlertDialog {\n    pub fn new(title: impl Into<SharedString>) -> Self {\n        // implementation\n    }\n\n    // Builder methods\n    pub fn description(mut self, desc: impl Into<SharedString>) -> Self {\n        // implementation\n    }\n}\n```\n\n### 2. Register in lib.rs\n\nAdd the module to `crates/ui/src/lib.rs`:\n\n```rust\npub mod alert_dialog;\n```\n\n### 3. Extend WindowExt (if needed)\n\nFor dialog-like components, add helper methods to `window_ext.rs`:\n\n```rust\npub trait WindowExt {\n    fn open_alert_dialog(&mut self, alert: AlertDialog, cx: &mut App);\n}\n```\n\n### 4. Create Story\n\nCreate `crates/story/src/stories/alert_dialog_story.rs`:\n\n```rust\npub struct AlertDialogStory {\n    focus_handle: FocusHandle,\n}\n\nimpl Story for AlertDialogStory {\n    fn title() -> &'static str {\n        \"AlertDialog\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n```\n\n### 5. Register Story\n\nAdd to `crates/story/src/stories/mod.rs`:\n\n```rust\nmod alert_dialog_story;\npub use alert_dialog_story::AlertDialogStory;\n```\n\nAdd to `crates/story/src/main.rs` in the stories list:\n\n```rust\nvec![\n    StoryContainer::panel::<AlertStory>(window, cx),\n    StoryContainer::panel::<AlertDialogStory>(window, cx),  // Add here\n    // ...\n]\n```\n\n## Real Example: AlertDialog\n\nAlertDialog is a composite component based on Dialog with these features:\n\n1. **Simpler API**: Pre-configured for common alert scenarios\n2. **Center-aligned layout**: All content (icon, title, description, buttons) is center-aligned\n3. **Vertical layout**: Icon appears at the top, followed by title and description\n4. **Auto icons**: Automatically shows icons based on variant (Info, Success, Warning, Error)\n5. **Convenience constructors**: `AlertDialog::info()`, `AlertDialog::warning()`, etc.\n\n**Key Design Decisions**:\n- `description` uses `SharedString` instead of `AnyElement` because the Dialog builder needs to be `Fn` (callable multiple times), and `AnyElement` cannot be cloned\n- Implementation is in `window_ext.rs` using Dialog as the base, not as a separate IntoElement component\n- **Center-aligned layout**: Icon is positioned at the top (not left), all text is center-aligned for a more focused alert appearance\n- **Footer center-aligned**: Buttons are centered, different from Dialog's default right-aligned footer\n\n**Usage**:\n```rust\nwindow.open_alert_dialog(\n    AlertDialog::warning(\"Unsaved Changes\")\n        .description(\"You have unsaved changes.\")\n        .show_cancel(true)\n        .on_confirm(|_, window, cx| {\n            window.push_notification(\"Confirmed\", cx);\n            true\n        }),\n    cx,\n);\n```\n\n## Common Patterns\n\n### Builder Pattern\nAll components use the builder pattern for configuration:\n```rust\nAlertDialog::new(\"Title\")\n    .description(\"Description\")\n    .width(px(500.))\n    .on_confirm(|_, _, _| true)\n```\n\n### Size Variants\nImplement `Sizable` trait for components that support size variants (xs, sm, md, lg).\n\n### Variants\nUse enums for visual variants (e.g., `AlertVariant::Info`, `ButtonVariant::Primary`).\n\n### Styled Trait Implementation\n\nComponents that render as a single container element should implement `Styled` to allow callers to customize styles. The pattern uses a `StyleRefinement` field and `refine_style()` from `StyledExt`:\n\n```rust\nuse gpui::{AnyElement, App, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, Window, div};\nuse crate::StyledExt as _;\n\n#[derive(IntoElement)]\npub struct MyComponent {\n    style: StyleRefinement,\n    children: Vec<AnyElement>,\n}\n\nimpl MyComponent {\n    pub fn new() -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            children: Vec::new(),\n        }\n    }\n}\n\nimpl ParentElement for MyComponent {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Styled for MyComponent {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for MyComponent {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        div()\n            // ... component's default styles ...\n            .refine_style(&self.style)  // Apply user's style overrides\n            .children(self.children)\n    }\n}\n```\n\nKey points:\n- Add `style: StyleRefinement` field initialized with `StyleRefinement::default()`\n- Implement `Styled` trait returning `&mut self.style`\n- In `render()`, call `.refine_style(&self.style)` on the root div to merge user styles\n- Place `.refine_style()` after component defaults but before `.children()` so user styles override defaults\n- Reference: `crates/ui/src/dialog/header.rs` (DialogHeader), `crates/ui/src/table/table.rs` (Table and sub-components)\n\n### Callbacks\nUse `Rc<dyn Fn>` for callbacks that may be called multiple times:\n```rust\non_confirm: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static>>\n```\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/01_bug.md",
    "content": "---\nname: Bug report\nabout: Report a problem or unexpected behavior\ntitle: \"\"\nassignees:\n---\n\n## Description\n\nUse English with clear and concise description of the bug, if you just have a question or need help, please submit to [Discussions](https://github.com/longbridge/gpui-component/discussions/categories/q-a).\n\n## Environment\n\n- GPUI: [e.g., v0.2.2]\n- GPUI Component: [e.g., v0.4.0]\n- Platform: [e.g., macOS 26, Windows 11, Ubuntu 20.04]\n\n## Steps to Reproduce\n\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n## Screenshots\n\nIf applicable, add screenshots to help explain your problem.\n\n## Expected\n\nA clear and concise description of what you expected to happen.\n\n## Actual\n\nA clear and concise description of what actually happened.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"cargo\"\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"weekly\"\n    allow:\n      - dependency-name: gpui\n      - dependency-name: wry\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "Closes #[issue number]\n\n## Description\n\nDescribe in English for the changes made in this pull request and the problem it solves.\nPlease keep **1 PR to solve 1 problem**, and keep **Small improvements should be small modifications** to make PR easier to review and to merge.\n\n## Screenshot\n\n| Before                       | After                       |\n| ---------------------------- | --------------------------- |\n| [Put Before Screenshot here] | [Put After Screenshot here] |\n\n## Break Changes\n\nDescribe any breaking changes introduced by this pull request. If none, remove this section.\n\n- Change 1\n\n```diff\n- Old code snippet\n+ New code snippet\n```\n\n## How to Test\n\nPlease describe the tests that you ran to verify your changes. Provide instructions so we can reproduce.\n\n## Checklist\n\n- [ ] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and followed the guidelines.\n- [ ] Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate.\n- [ ] Passed `cargo run` for story tests related to the changes.\n- [ ] Tested macOS, Windows and Linux platforms performance (if the change is platform-specific)\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non:\n  pull_request:\n  push:\n    branches:\n      - main\n    tags:\n      - \"*\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}\n  cancel-in-progress: true\n\njobs:\n  test:\n    name: Test\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - target: aarch64-apple-darwin\n            run_on: macos-latest\n          - target: x86_64-linux-gnu\n            run_on: ubuntu-latest\n          - target: x86_64-windows-msvc\n            run_on: windows-latest\n    runs-on: ${{ matrix.run_on }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions-rust-lang/setup-rust-toolchain@v1\n        with:\n          components: clippy\n      - name: Install system dependencies\n        if: ${{ matrix.run_on != 'windows-latest' }}\n        run: script/bootstrap\n      - name: Machete\n        if: ${{ matrix.run_on == 'macos-latest' }}\n        uses: bnjbvr/cargo-machete@v0.9.1\n      - name: Setup | Cache Cargo\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.cargo/bin/\n            ~/.cargo/registry/index/\n            ~/.cargo/registry/cache/\n            ~/.cargo/git/db/\n            target/\n          key: test-cargo-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}\n      - name: Typo check\n        if: ${{ matrix.run_on == 'macos-latest' }}\n        run: |\n          cargo install typos-cli || echo \"typos-cli already installed\"\n          typos\n      - name: Lint\n        if: ${{ matrix.run_on == 'macos-latest' }}\n        run: |\n          cargo clippy -- --deny warnings\n      - name: Test Linux\n        if: ${{ matrix.run_on == 'ubuntu-latest' }}\n        run: |\n          cargo test --all\n      - name: Test Windows\n        if: ${{ matrix.run_on == 'windows-latest' }}\n        run: |\n          cargo test --all\n      - name: Test MacOS\n        if: ${{ matrix.run_on == 'macos-latest' }}\n        run: |\n          cargo test --all\n          cargo test -p gpui-component --doc\n"
  },
  {
    "path": ".github/workflows/release-docs.yml",
    "content": "name: Release Docs\non:\n  workflow_run:\n    workflows: [\"Release Crate\"]\n    types:\n      - completed\n  workflow_dispatch:\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.workflow_run.head_sha }}\n\n      - uses: oven-sh/setup-bun@v1\n\n      - name: Setup Rust\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: wasm32-unknown-unknown\n\n      - name: Install wasm-bindgen-cli\n        run: cargo install -f wasm-bindgen-cli --version 0.2.113\n\n      - name: Cache Cargo dependencies\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.cargo/registry\n            ~/.cargo/git\n            target\n          key: ${{ runner.os }}-cargo-wasm-${{ hashFiles('**/Cargo.lock') }}\n\n      - name: Build Story Web (WASM)\n        working-directory: crates/story-web\n        run: |\n          make build-prod\n\n      - name: Build Docs\n        working-directory: docs\n        run: |\n          bun install\n          bun run build\n          cp .vitepress/dist/index.html .vitepress/dist/404.html\n\n      - name: Copy Story Web to Docs\n        run: |\n          mkdir -p docs/.vitepress/dist/gallery\n          cp -r crates/story-web/www/dist/* docs/.vitepress/dist/gallery/\n\n      - name: Setup Pages\n        uses: actions/configure-pages@v5\n\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@v3\n        with:\n          path: \"docs/.vitepress/dist\"\n\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: \"Release Crate\"\non:\n  push:\n    tags:\n      - \"v*\"\njobs:\n  publish_crate:\n    name: Publish Crate\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions-rust-lang/setup-rust-toolchain@v1\n      - name: Release Crate\n        run: |\n          cargo publish --workspace --token ${{ secrets.CARGO_REGISTRY_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/test-docs.yml",
    "content": "name: Test Docs\non:\n  pull_request:\n    paths:\n      - \"docs/**\"\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - uses: oven-sh/setup-bun@v1\n      - name: Install\n        working-directory: docs\n        run: bun install\n      - name: Build\n        working-directory: docs\n        run: |\n          bun run build\n"
  },
  {
    "path": ".gitignore",
    "content": "target/\n.DS_Store\n/docks.json\n/profile.json\n.vscode\nindex.scip\n*.log\nnode_modules/\n\n# WASM build artifacts\ncrates/story-web/www/src/wasm/*.js\ncrates/story-web/www/src/wasm/*.wasm\ncrates/story-web/www/src/wasm/*.ts\n!crates/story-web/www/src/wasm/.gitkeep\n\n# Frontend build artifacts\ncrates/story-web/www/dist/\ncrates/story-web/www/bun.lockb\n"
  },
  {
    "path": ".rustfmt.toml",
    "content": "edition = \"2024\"\nstyle_edition = \"2024\"\n"
  },
  {
    "path": ".theme-schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"title\": \"ThemeSet\",\n  \"description\": \"Represents a theme configuration.\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"description\": \"The name of the theme set.\",\n      \"type\": \"string\",\n      \"default\": \"\"\n    },\n    \"author\": {\n      \"description\": \"The author of the theme.\",\n      \"type\": [\"string\", \"null\"],\n      \"default\": null\n    },\n    \"url\": {\n      \"description\": \"The URL of the theme.\",\n      \"type\": [\"string\", \"null\"],\n      \"default\": null\n    },\n    \"themes\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/$defs/ThemeConfig\"\n      },\n      \"default\": []\n    }\n  },\n  \"required\": [\"name\", \"themes\"],\n  \"$defs\": {\n    \"ThemeConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"description\": \"The name of the theme.\",\n          \"type\": \"string\",\n          \"default\": \"\"\n        },\n        \"mode\": {\n          \"description\": \"The mode of the theme, default is light.\",\n          \"$ref\": \"#/$defs/ThemeMode\",\n          \"default\": \"light\"\n        },\n        \"font.size\": {\n          \"description\": \"The base font size, default is 16.\",\n          \"type\": [\"number\", \"null\"],\n          \"format\": \"float\",\n          \"default\": 16\n        },\n        \"font.family\": {\n          \"description\": \"The base font family, default is system font: `.SystemUIFont`.\",\n          \"type\": [\"string\", \"null\"],\n          \"default\": \".SystemUIFont\"\n        },\n        \"mono_font.size\": {\n          \"description\": \"The base monospace font size, default is 13.\",\n          \"type\": [\"number\", \"null\"],\n          \"format\": \"float\",\n          \"default\": 13\n        },\n        \"mono_font.family\": {\n          \"description\": \"The monospace font family, default is platform specific:\\nmacOS: `Menlo`\\n- Windows: `Consolas`\\n- Linux: `DejaVu Sans Mono`\",\n          \"type\": [\"string\", \"null\"],\n          \"default\": null\n        },\n        \"radius\": {\n          \"description\": \"The border radius for general elements, default is 6.\",\n          \"type\": [\"number\", \"null\"],\n          \"default\": 6\n        },\n        \"radius.lg\": {\n          \"description\": \"The border radius for large elements like Dialogs and Notifications, default is 8.\",\n          \"type\": [\"number\", \"null\"],\n          \"default\": 8\n        },\n        \"shadow\": {\n          \"description\": \"Set shadows in the theme, for example the Input and Button, default is true.\",\n          \"type\": [\"boolean\", \"null\"],\n          \"default\": true\n        },\n        \"colors\": {\n          \"description\": \"The colors of the theme.\",\n          \"$ref\": \"#/$defs/ThemeConfigColors\",\n          \"default\": {\n            \"accent.background\": null,\n            \"accent.foreground\": null,\n            \"background\": null,\n            \"border\": null,\n            \"danger.background\": null,\n            \"foreground\": null,\n            \"muted.background\": null,\n            \"muted.foreground\": null,\n            \"primary.background\": null,\n            \"primary.foreground\": null,\n            \"secondary.background\": null,\n            \"secondary.foreground\": null,\n            \"base.blue\": null,\n            \"base.cyan\": null,\n            \"base.green\": null,\n            \"base.magenta\": null,\n            \"base.red\": null,\n            \"base.yellow\": null\n          }\n        },\n        \"highlight\": {\n          \"description\": \"The highlight theme, this part is combilbility with `style` section in Zed theme.\\n\\nhttps://github.com/zed-industries/zed/blob/f50041779dcfd7a76c8aec293361c60c53f02d51/assets/themes/ayu/ayu.json#L9\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/HighlightThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": null\n        }\n      },\n      \"required\": [\"name\", \"mode\", \"colors\"]\n    },\n    \"ThemeMode\": {\n      \"type\": \"string\",\n      \"enum\": [\"light\", \"dark\"]\n    },\n    \"ThemeConfigColors\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"accent.background\": {\n          \"description\": \"Used for accents such as hover background on MenuItem, ListItem, etc.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"accent.foreground\": {\n          \"description\": \"Used for accent text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"accordion.background\": {\n          \"description\": \"Accordion background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"accordion.hover.background\": {\n          \"description\": \"Accordion hover background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"background\": {\n          \"description\": \"Default background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"border\": {\n          \"description\": \"Default border color\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"caret\": {\n          \"description\": \"Input caret color (Blinking cursor).\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"chart.1\": {\n          \"description\": \"Chart 1 color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"chart.2\": {\n          \"description\": \"Chart 2 color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"chart.3\": {\n          \"description\": \"Chart 3 color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"chart.4\": {\n          \"description\": \"Chart 4 color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"chart.5\": {\n          \"description\": \"Chart 5 color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"danger.background\": {\n          \"description\": \"Danger background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"danger.active.background\": {\n          \"description\": \"Danger active background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"danger.foreground\": {\n          \"description\": \"Danger text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"danger.hover.background\": {\n          \"description\": \"Danger hover background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"description_list.label.background\": {\n          \"description\": \"Description List label background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"description_list.label.foreground\": {\n          \"description\": \"Description List label foreground color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"drag.border\": {\n          \"description\": \"Drag border color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"drop_target.background\": {\n          \"description\": \"Drop target background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"foreground\": {\n          \"description\": \"Default text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"group_box.background\": {\n          \"description\": \"Background color for GroupBox.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"group_box.foreground\": {\n          \"description\": \"Foreground color for GroupBox.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"group_box.title.foreground\": {\n          \"description\": \"Title text color for GroupBox.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"info.background\": {\n          \"description\": \"Info background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"info.active.background\": {\n          \"description\": \"Info active background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"info.foreground\": {\n          \"description\": \"Info text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"info.hover.background\": {\n          \"description\": \"Info hover background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"input.border\": {\n          \"description\": \"Border color for inputs such as Input, Select, etc.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"link\": {\n          \"description\": \"Link text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"link.active\": {\n          \"description\": \"Active link text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"link.hover\": {\n          \"description\": \"Hover link text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"list.background\": {\n          \"description\": \"Background color for List and ListItem.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"list.active.background\": {\n          \"description\": \"Background color for active ListItem.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"list.active.border\": {\n          \"description\": \"Border color for active ListItem.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"list.even.background\": {\n          \"description\": \"Stripe background color for even ListItem.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"list.head.background\": {\n          \"description\": \"Background color for List header.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"list.hover.background\": {\n          \"description\": \"Hover background color for ListItem.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"muted.background\": {\n          \"description\": \"Muted backgrounds such as Skeleton and Switch.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"muted.foreground\": {\n          \"description\": \"Muted text color, as used in disabled text.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"popover.background\": {\n          \"description\": \"Background color for Popover.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"popover.foreground\": {\n          \"description\": \"Text color for Popover.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"primary.background\": {\n          \"description\": \"Primary background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"primary.active.background\": {\n          \"description\": \"Active primary background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"primary.foreground\": {\n          \"description\": \"Primary text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"primary.hover.background\": {\n          \"description\": \"Hover primary background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"progress.bar.background\": {\n          \"description\": \"Progress bar background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"ring\": {\n          \"description\": \"Used for focus ring.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"scrollbar.background\": {\n          \"description\": \"Scrollbar background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"scrollbar.thumb.background\": {\n          \"description\": \"Scrollbar thumb background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"scrollbar.thumb.hover.background\": {\n          \"description\": \"Scrollbar thumb hover background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"secondary.background\": {\n          \"description\": \"Secondary background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"secondary.active.background\": {\n          \"description\": \"Active secondary background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"secondary.foreground\": {\n          \"description\": \"Secondary text color, used for secondary Button text color or secondary text.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"secondary.hover.background\": {\n          \"description\": \"Hover secondary background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"selection.background\": {\n          \"description\": \"Input selection background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"sidebar.background\": {\n          \"description\": \"Sidebar background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"sidebar.accent.background\": {\n          \"description\": \"Sidebar accent background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"sidebar.accent.foreground\": {\n          \"description\": \"Sidebar accent text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"sidebar.border\": {\n          \"description\": \"Sidebar border color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"sidebar.foreground\": {\n          \"description\": \"Sidebar text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"sidebar.primary.background\": {\n          \"description\": \"Sidebar primary background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"sidebar.primary.foreground\": {\n          \"description\": \"Sidebar primary text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"skeleton.background\": {\n          \"description\": \"Skeleton background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"slider.background\": {\n          \"description\": \"Slider bar background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"slider.thumb.background\": {\n          \"description\": \"Slider thumb background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"success.background\": {\n          \"description\": \"Success background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"success.foreground\": {\n          \"description\": \"Success text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"success.hover.background\": {\n          \"description\": \"Success hover background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"success.active.background\": {\n          \"description\": \"Success active background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"switch.background\": {\n          \"description\": \"Switch background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"switch.thumb.background\": {\n          \"description\": \"Switch thumb background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"tab.background\": {\n          \"description\": \"Tab background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"tab.active.background\": {\n          \"description\": \"Tab active background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"tab.active.foreground\": {\n          \"description\": \"Tab active text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"tab_bar.background\": {\n          \"description\": \"TabBar background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"tab_bar.segmented.background\": {\n          \"description\": \"TabBar segmented background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"tab.foreground\": {\n          \"description\": \"Tab text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"table.background\": {\n          \"description\": \"Table background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"table.active.background\": {\n          \"description\": \"Table active item background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"table.active.border\": {\n          \"description\": \"Table active item border color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"table.even.background\": {\n          \"description\": \"Stripe background color for even TableRow.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"table.head.background\": {\n          \"description\": \"Table header background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"table.head.foreground\": {\n          \"description\": \"Table header text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"table.foot.background\": {\n          \"description\": \"Table footer background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"table.foot.foreground\": {\n          \"description\": \"Table footer text color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"table.hover.background\": {\n          \"description\": \"Table item hover background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"table.row.border\": {\n          \"description\": \"Table row border color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"title_bar.background\": {\n          \"description\": \"TitleBar background color, use for Window title bar.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"title_bar.border\": {\n          \"description\": \"TitleBar border color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"tiles.background\": {\n          \"description\": \"Background color for Tiles.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"warning.background\": {\n          \"description\": \"Warning background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"warning.active.background\": {\n          \"description\": \"Warning active background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"warning.hover.background\": {\n          \"description\": \"Warning hover background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"warning.foreground\": {\n          \"description\": \"Warning foreground color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"overlay\": {\n          \"description\": \"Overlay background color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"window.border\": {\n          \"description\": \"Window border color.\\n\\n# Platform specific:\\n\\nThis is only works on Linux, other platforms we can't change the window border color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"base.blue\": {\n          \"description\": \"Base blue color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"base.blue.light\": {\n          \"description\": \"Base light blue color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"base.cyan\": {\n          \"description\": \"Base cyan color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"base.cyan.light\": {\n          \"description\": \"Base light cyan color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"base.green\": {\n          \"description\": \"Base green color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"base.green.light\": {\n          \"description\": \"Base light green color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"base.magenta\": {\n          \"description\": \"Base magenta color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"base.magenta.light\": {\n          \"type\": [\"string\", \"null\"]\n        },\n        \"base.red\": {\n          \"description\": \"Base red color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"base.red.light\": {\n          \"description\": \"Base light red color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"base.yellow\": {\n          \"description\": \"Base yellow color.\",\n          \"type\": [\"string\", \"null\"]\n        },\n        \"base.yellow.light\": {\n          \"description\": \"Base light yellow color.\",\n          \"type\": [\"string\", \"null\"]\n        }\n      },\n      \"required\": [\n        \"accent.background\",\n        \"accent.foreground\",\n        \"background\",\n        \"border\",\n        \"foreground\",\n        \"muted.background\",\n        \"muted.foreground\",\n        \"primary.background\",\n        \"primary.foreground\",\n        \"base.blue\",\n        \"base.cyan\",\n        \"base.green\",\n        \"base.magenta\",\n        \"base.red\",\n        \"base.yellow\"\n      ]\n    },\n    \"HighlightThemeStyle\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"editor.background\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"editor.foreground\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"editor.active_line.background\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"editor.line_number\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"editor.active_line_number\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"error\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"error.background\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"error.border\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"warning\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"warning.background\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"warning.border\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"info\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"info.background\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"info.border\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"success\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"success.background\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"success.border\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"hint\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"hint.background\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"hint.border\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"syntax\": {\n          \"$ref\": \"#/$defs/SyntaxColors\"\n        }\n      },\n      \"required\": [\"syntax\"]\n    },\n    \"Rgba\": {\n      \"type\": \"string\",\n      \"pattern\": \"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$\"\n    },\n    \"SyntaxColors\": {\n      \"description\": \"Theme for Tree-sitter Highlight\\n\\nhttps://docs.rs/tree-sitter-highlight/0.25.4/tree_sitter_highlight/\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"attribute\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"boolean\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"comment\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"comment_doc\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"constant\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"constructor\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"embedded\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"emphasis\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"emphasis.strong\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"enum\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"function\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"hint\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"keyword\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"label\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"link_text\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"link_uri\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"number\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"operator\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"predictive\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"preproc\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"primary\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"property\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"punctuation\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"punctuation.bracket\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"punctuation.delimiter\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"punctuation.list_marker\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"punctuation.special\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"string\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"string.escape\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"string.regex\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"string.special\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"string.special.symbol\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"tag\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"tag.doctype\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"text.literal\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"title\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"type\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"variable\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"variable.special\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"variant\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"required\": []\n    },\n    \"ThemeStyle\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"color\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rgba\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"font_style\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/FontStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"font_weight\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/FontWeightContent\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"required\": []\n    },\n    \"FontStyle\": {\n      \"type\": \"string\",\n      \"enum\": [\"normal\", \"italic\", \"underline\"]\n    },\n    \"FontWeightContent\": {\n      \"type\": \"number\",\n      \"enum\": [100, 200, 300, 400, 500, 600, 700, 800, 900]\n    }\n  }\n}\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nGPUI Component is a UI component library for building desktop applications using [GPUI](https://gpui.rs). It provides 60+ cross-platform desktop UI components, inspired by macOS/Windows controls and combined with shadcn/ui design.\n\nThis is a Rust workspace project with the following main crates:\n\n- `crates/ui` - Core UI component library (published as `gpui-component`)\n- `crates/story` - Gallery application for showcasing and testing components\n- `crates/story-web` - Web version of the story gallery (using WebAssembly)\n- `crates/macros` - Procedural macros (`IntoPlot` derive)\n- `crates/assets` - Static assets\n- `crates/webview` - WebView component support\n- `examples/` - Various example applications\n\n## Common Commands\n\n### Development and Testing\n\n```bash\n# Run Story Gallery (component showcase application)\ncargo run\n\n# Run individual examples\ncargo run --example hello_world\ncargo run --example table\n\n# Build the project\ncargo build\n\n# Lint check\ncargo clippy -- --deny warnings\n\n# Format check\ncargo fmt --check\n\n# Spell check\ntypos\n\n# Check for unused dependencies\ncargo machete\n```\n\n### Testing\n\n**Note**: Per user configuration, tests do not need to be run.\n\n```bash\n# Run all tests\ncargo test --all\n\n# Run tests for a specific crate\ncargo test -p gpui-component\n\n# Run doc tests\ncargo test -p gpui-component --doc\n```\n\n### Performance Profiling\n\n```bash\n# View FPS on macOS (using Metal HUD)\nMTL_HUD_ENABLED=1 cargo run\n\n# Profile performance using samply\nsamply record cargo run\n```\n\n## Core Architecture\n\n### Component Initialization\n\n**Critical requirement**: You must call `gpui_component::init(cx)` at your application's entry point before using any GPUI Component features.\n\n```rust\nfn main() {\n    let app = Application::new();\n    app.run(move |cx| {\n        // This must be called first\n        gpui_component::init(cx);\n\n        cx.spawn(async move |cx| {\n            cx.open_window(WindowOptions::default(), |window, cx| {\n                let view = cx.new(|_| MyView);\n                // The first level view in a window must be a Root\n                cx.new(|cx| Root::new(view, window, cx))\n            })\n            .expect(\"Failed to open window\");\n        }).detach();\n    });\n}\n```\n\n### Root View System\n\n`Root` is the top-level view for a window and manages:\n\n- Sheet (side panels)\n- Dialog (dialogs)\n- Notification (notifications)\n- Keyboard navigation (Tab/Shift-Tab)\n\nThe first view of every window must be a `Root`.\n\n### Theme System\n\n- Uses `Theme` global singleton for theme configuration\n- Supports light/dark mode switching\n- Access theme via `ActiveTheme` trait: `cx.theme()`\n- Theme configuration includes:\n  - Colors (`ThemeColor`)\n  - Syntax highlighting theme (`HighlightTheme`)\n  - Font configuration (system font and monospace font)\n  - UI parameters like border radius, shadows\n  - Scrollbar display mode\n\n### Dock System\n\nA complex panel layout system supporting:\n\n- **DockArea**: Main container managing center area and left/bottom/right docks\n- **DockItem**: Tree-based layout structure\n  - `Split`: Split layout (horizontal/vertical)\n  - `Tabs`: Tab layout\n  - `Panel`: Individual panel\n- **Panel**: Defined via `PanelView` trait\n- **PanelRegistry**: Global panel registry for serializing/deserializing layouts\n- **StackPanel**: Resizable split panel container\n- **TabPanel**: Tab panel container\n\nThe Dock system supports:\n\n- Panel drag-and-drop reordering\n- Panel zoom\n- Layout locking\n- Layout serialization/restoration\n\n### Input System\n\nText input system based on Rope data structure:\n\n- **InputState**: Input state management\n- **Rope**: Efficient text storage (from ropey crate)\n- LSP integration support (diagnostics, completion, hover)\n- Syntax highlighting support (Tree-sitter)\n- Multiple input modes:\n  - Regular input (`Input`)\n  - Number input (`NumberInput`)\n  - OTP input (`OtpInput`)\n\n### Component Design Principles\n\n1. **Stateless design**: Use `RenderOnce` trait, components should be stateless when possible\n2. **Size system**: Supports `xs`, `sm`, `md` (default), `lg` sizes via `Sizable` trait.\n3. **Mouse cursor**: Buttons use `default` cursor not `pointer` (desktop app convention), unless it's a link button\n4. **Style system**: Provides CSS-like styling API via `Styled` trait and `ElementExt` extensions\n\n## Code Style\n\n- Follow naming and organization patterns from existing code\n- Reference macOS/Windows control API design for naming\n- AI-generated code must be refactored to match project style\n- Mark AI-generated portions when submitting PRs\n\n## Icon System\n\nThe `Icon` element does not include SVG files by default. You need to:\n\n- Use [Lucide](https://lucide.dev) or other icon libraries\n- Name SVG files according to the `IconName` enum definition (located in `crates/ui/src/icon.rs`)\n\n## Dependencies\n\n- GPUI: Git version from Zed repository\n- Tree-sitter: For syntax highlighting\n- Ropey: Rope data structure for text, and `RopeExt` trait with more features.\n- Markdown rendering: `markdown` crate\n- HTML rendering: `html5ever` (basic support)\n- Charts: Built-in chart components\n- LSP: `lsp-types` crate\n\n## Internationalization\n\nUses `rust-i18n` crate.\n\n- Localization files are located in `crates/ui/locales/`.\n- Only add `en`, `zh-CN`, `zh-HK` by default.\n\n## Platform Support\n\n- macOS (aarch64, x86_64)\n- Linux (x86_64)\n- Windows (x86_64)\n\nCI runs full test suite on each platform.\n\n## Skills Reference\n\nThis project has custom Claude Code skills in `.claude/skills/` to assist with common development tasks:\n\n### Component Development Skills\n\n- **new-component** - Creating new GPUI components with proper structure and patterns\n- **generate-component-story** - Creating story examples for components in the gallery\n- **generate-component-documentation** - Generating documentation for components\n\n### GPUI Framework Skills\n\n- **gpui-action** - Working with actions and keyboard shortcuts\n- **gpui-async** - Async operations and background tasks\n- **gpui-context** - Context management (App, Window, AsyncApp)\n- **gpui-element** - Implementing custom elements using low-level Element API\n- **gpui-entity** - Entity management and state handling\n- **gpui-event** - Event handling and subscriptions\n- **gpui-focus-handle** - Focus management and keyboard navigation\n- **gpui-global** - Global state management\n- **gpui-layout-and-style** - Layout and styling systems\n- **gpui-test** - Writing tests for GPUI applications\n\n### Other Skills\n\n- **github-pull-request-description** - Writing PR descriptions\n\nWhen working on tasks related to these areas, Claude Code will automatically use the appropriate skill to provide specialized guidance and patterns.\n\n## Testing Guidelines\n\nSee `.claude/COMPONENT_TEST_RULES.md` for detailed testing principles:\n\n- **Simplicity First**: Focus on complex logic and core functionality, avoid excessive simple tests\n- **Builder Pattern Testing**: Every component should have a `test_*_builder` test covering the builder pattern\n- **Complex Logic Testing**: Test conditional branching, state transitions, and edge cases\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing Guide\n\nContributions are welcome, if you find some bugs or have some ideas, please open an issue or submit a pull request.\n\nPlease ensure that you are using clean code, following the coding style and code organization in existing code, and make sure all the tests pass.\n\nPlease submit **one PR that does one thing**, this is important, and helps us to review your code more easily and push to merge fast.\n\n## AI Assistance\n\n> 🤖 When you submit PR, please point out which parts are generated by AI, if any.\n\nAll code generated by AI must be reviewed and tested by humans, and should follow the same coding style and code organization as existing code.\n\nThe AI generated code without refactoring will be rejected.\n\n## Code Style\n\nBefore you start to write code, please read the existing code to follow the same coding style and code organization.\n\n- Inspired by existing code or refer to macOS/Windows controls API design to name your functions, properties, structs etc.\n\n## Development and Testing\n\n### System dependencies\n\nThe `script` folder contains some useful scripts to help you set up the development environment.\n\nTo install the system dependencies, run the following script:\n\n```bash\n./script/bootstrap\n```\n\nFor Windows, you can run the following command in PowerShell:\n\n```powershell\n.\\script\\install-window.ps1\n```\n\n### Run story\n\nThere are a lot of UI test cases in the `crates/story` folder, if you change the existing features you can run the tests to make sure they are working.\n\nUse `cargo run` to run the complete story examples to display them all in a gallery of GPUI components.\n\n```bash\ncargo run\n```\n\n### Run single example\n\nThere is also available some split examples, run `cargo run --example` to see the available examples.\n\n```bash\ncargo run --example table\n```\n\n## UI Guides\n\nGPUI Component is inspired by macOS and Windows controls, combined with shadcn/ui design for a modern experience.\n\nSo please refer to the following UI guides when you design or change the UI components:\n\n- [Apple Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/)\n- [Microsoft Fluent Design System](https://learn.microsoft.com/en-us/windows/apps/design/)\n- [shadcn/ui](https://ui.shadcn.com/)\n\n### Rules\n\n- Use `default` mouse cursor not `pointer` for buttons, unless it's a link button, we are building desktop apps, not web apps.\n- Use `md` size for most cases and as the default.\n\n## Profile the performance\n\nWhen you change the rendering code, please profile the performance to make sure the FPS is still good.\n\nYou can use `MTL_HUD_ENABLED=1` environment variable to enable the Metal HUD to see the FPS and other performance metrics.\n\n```bash\nMTL_HUD_ENABLED=1 cargo run\n```\n\n> NOTE: Only available on macOS with Metal backend, and the FPS is up **limited your monitor refresh rate**, usually 60 or 120.\n\n### Use Samply to profile the the performance\n\nYou can use [Samply](https://github.com/mstange/samply) to profile the performance of the application to get more detailed information.\n\n```bash\nsamply record cargo run\n```\n\nUse `samply record` command to start rust development, and do some operations in the app that you want to profile, then stop the terminal with `ctrl-c`, then samply will open the browser to show the profile results.\n\n## Release crates version\n\nWhen we are ready to release a new version, please follow the steps below:\n\n### Use the script to bump the version(Recommended)\n\n```bash\n./script/bump-version.sh x.y.z\n```\n\n### Manually bump the version\n\n1. Run `cargo set-version` to set the new version for all crates.\n\n   ```bash\n   cargo set-version x.y.z\n   ```\n\n2. Git Commit the changes with message `Bump vx.y.z`.\n3. Create a new git tag with the version `vx.y.z` and push `main` branch and the tag to remote.\n\n   ```bash\n   git tag vx.y.z\n   git push origin vx.y.z\n   ```\n\n4. Then GitHub Actions will automatically publish the crates to crates.io and create a new release in GitHub.\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\ndefault-members = [\"crates/ui\", \"crates/story\", \"crates/assets\"]\nmembers = [\n    \"crates/macros\",\n    \"crates/story\",\n    \"crates/story-web\",\n    \"crates/ui\",\n    \"crates/assets\",\n    \"crates/webview\",\n    \"examples/app_assets\",\n    \"examples/hello_world\",\n    \"examples/input\",\n    \"examples/window_title\",\n    \"examples/dialog_overlay\",\n    \"examples/webview\",\n    \"examples/system_monitor\",\n    \"examples/focus_trap\",\n]\nresolver = \"2\"\n\n[workspace.package]\npublish = false\nedition = \"2024\"\n\n[workspace.dependencies]\ngpui-component = { path = \"crates/ui\", version = \"0.5.1\" }\ngpui-component-macros = { path = \"crates/macros\", version = \"0.5.1\" }\ngpui-component-assets = { path = \"crates/assets\", version = \"0.5.1\" }\nstory = { path = \"crates/story\" }\n\ngpui = { git = \"https://github.com/zed-industries/zed\" }\ngpui_platform = { git = \"https://github.com/zed-industries/zed\", features = [\"font-kit\", \"x11\", \"wayland\", \"runtime_shaders\"] }\ngpui_web = { git = \"https://github.com/zed-industries/zed\" }\ngpui_macros = { git = \"https://github.com/zed-industries/zed\" }\nreqwest_client = { git = \"https://github.com/zed-industries/zed\" }\nsum-tree = { version = \"0.2.0\", package = \"zed-sum-tree\" }\n# reqwest = { version = \"0.12.15-zed\", package = \"zed-reqwest\" }\nreqwest = { git = \"https://github.com/zed-industries/reqwest.git\", rev = \"c15662463bda39148ba154100dd44d3fba5873a4\", default-features = false, features = [\n    \"charset\",\n    \"http2\",\n    \"macos-system-configuration\",\n    \"multipart\",\n    \"rustls-tls-native-roots\",\n    \"socks\",\n    \"stream\",\n], package = \"zed-reqwest\", version = \"0.12.15-zed\" }\n\nanyhow = \"1\"\nlog = \"0.4\"\nlsp-types = { version = \"0.97.0\", features = [\"proposed\"] }\nnotify = \"7.0.0\"\nraw-window-handle = \"0.6.2\"\nropey = { version = \"=2.0.0-beta.1\", features = [\n    \"metric_lines_lf\",\n    \"metric_utf16\",\n] }\nrust-i18n = \"3\"\nschemars = \"1\"\nserde = { version = \"1.0.219\", features = [\"derive\"] }\nserde_json = \"1\"\nserde_repr = \"0.1\"\nsmallvec = \"1\"\nsmol = \"2\"\ntracing = \"0.1.41\"\nwasm-bindgen = \"0.2.113\"\n# Use errno stub for WASM\nerrno = { version = \"0.3.14\", default-features = false }\n\n[workspace.dependencies.windows]\nfeatures = [\"Wdk\", \"Wdk_System\", \"Wdk_System_SystemServices\"]\nversion = \"0.58.0\"\n\n[workspace.lints.clippy]\nalmost_complete_range = \"allow\"\narc_with_non_send_sync = \"allow\"\nborrowed_box = \"allow\"\ndbg_macro = \"deny\"\nlet_underscore_future = \"allow\"\nmanual_is_multiple_of = \"allow\"\nmap_entry = \"allow\"\nmodule_inception = \"allow\"\nnon_canonical_partial_ord_impl = \"allow\"\nreversed_empty_ranges = \"allow\"\nsingle_range_in_vec_init = \"allow\"\nstyle = { level = \"allow\", priority = -1 }\ntodo = \"deny\"\ntype_complexity = \"allow\"\n# Temporarily allow this until we can migrate to the new APIs.\n# https://github.com/longbridge/gpui-component/pull/2149\nuseless_conversion = \"allow\"\n\n[profile.dev]\ncodegen-units = 16\ndebug = \"limited\"\nsplit-debuginfo = \"unpacked\"\n\n[profile.dev.package]\nresvg = { opt-level = 3 }\nrustybuzz = { opt-level = 3 }\ntaffy = { opt-level = 3 }\nttf-parser = { opt-level = 3 }\nsmol = { opt-level = 3 }\ngpui = { opt-level = 3 }\ngpui_platform = { opt-level = 3 }\ngpui_macros = { opt-level = 3 }\ntree-sitter = { opt-level = 3 }\nsum_tree = { opt-level = 3 }\nropey = { opt-level = 3 }\nlsp-types = { opt-level = 3 }\nreqwest_client = { opt-level = 3 }\nmarkdown = { opt-level = 3 }\nxml5ever = { opt-level = 3 }\n\n[workspace.metadata.typos]\nfiles.extend-exclude = [\"**/fixtures/*\"]\n\n[workspace.metadata.typos.default.extend-identifiers]\nconsts = \"consts\"\n\n[workspace.metadata.cargo-machete]\nignored = [\"log\", \"anyhow\", \"serde\"]\n\n# Patches for WASM compatibility\n[patch.crates-io]\n# Use stacker's psm version which may have better WASM support\npsm = { git = \"https://github.com/rust-lang/stacker\", branch = \"master\" }\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "Copyright 2024 - 2025 Longbridge <https://longbridge.com>\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n\n                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "Makefile",
    "content": "dev-web:\n\tcd crates/story-web && make dev\n"
  },
  {
    "path": "README.md",
    "content": "# GPUI Component\n\n[![Build Status](https://github.com/longbridge/gpui-component/actions/workflows/ci.yml/badge.svg)](https://github.com/longbridge/gpui-component/actions/workflows/ci.yml) [![Docs](https://docs.rs/gpui-component/badge.svg)](https://docs.rs/gpui-component/) [![Crates.io](https://img.shields.io/crates/v/gpui-component.svg)](https://crates.io/crates/gpui-component)\n\nUI components for building fantastic desktop applications using [GPUI](https://gpui.rs).\n\n## Features\n\n- **Richness**: 60+ cross-platform desktop UI components.\n- **Native**: Inspired by macOS and Windows controls, combined with shadcn/ui design for a modern experience.\n- **Ease of Use**: Stateless `RenderOnce` components, simple and user-friendly.\n- **Customizable**: Built-in `Theme` and `ThemeColor`, supporting multi-theme and variable-based configurations.\n- **Versatile**: Supports sizes like `xs`, `sm`, `md`, and `lg`.\n- **Flexible Layout**: Dock layout for panel arrangements, resizing, and freeform (Tiles) layouts.\n- **High Performance**: Virtualized Table and List components for smooth large-data rendering.\n- **Content Rendering**: Native support for Markdown and simple HTML.\n- **Charting**: Built-in charts for visualizing your data.\n- **Editor**: High performance code editor (support up to 200K lines) with LSP (diagnostics, completion, hover, etc).\n- **Syntax Highlighting**: Syntax highlighting for editor and markdown components using Tree Sitter.\n\n## Showcase\n\nhttps://longbridge.github.io/gpui-component/gallery/\n\nHere is the first application: [Longbridge Pro](https://longbridge.com/desktop), built using GPUI Component.\n\n<img width=\"1763\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/e1ecb9c3-2dd3-431e-bd97-5a819c30e551\" />\n\n## Usage\n\n```toml\ngpui = \"0.2.2\"\ngpui-component = \"0.5.1\"\n```\n\n### Basic Example\n\n```rs\nuse gpui::*;\nuse gpui_component::{button::*, *};\n\npub struct HelloWorld;\nimpl Render for HelloWorld {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .v_flex()\n            .gap_2()\n            .size_full()\n            .items_center()\n            .justify_center()\n            .child(\"Hello, World!\")\n            .child(\n                Button::new(\"ok\")\n                    .primary()\n                    .label(\"Let's Go!\")\n                    .on_click(|_, _, _| println!(\"Clicked!\")),\n            )\n    }\n}\n\nfn main() {\n    let app = Application::new();\n\n    app.run(move |cx| {\n        // This must be called before using any GPUI Component features.\n        gpui_component::init(cx);\n\n        cx.spawn(async move |cx| {\n            cx.open_window(WindowOptions::default(), |window, cx| {\n                let view = cx.new(|_| HelloWorld);\n                // This first level on the window, should be a Root.\n                cx.new(|cx| Root::new(view, window, cx))\n            })\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n```\n\n### Icons\n\nGPUI Component has an `Icon` element, but it does not include SVG files by default.\n\nThe example uses [Lucide](https://lucide.dev) icons, but you can use any icons you like. Just name the SVG files as defined in [IconName](https://github.com/longbridge/gpui-component/blob/main/crates/ui/src/icon.rs#L86). You can add any icons you need to your project.\n\n## Development\n\n### Desktop Gallery\n\nWe have a gallery of applications built with GPUI Component.\n\n```bash\ncargo run\n```\n\nMore examples can be found in the `examples` directory. You can run them with `cargo run --example <example_name>`.\n\n### Web Gallery (WASM)\n\nYou can also run the gallery in a web browser using WASM:\n\n```bash\ncd crates/story-web\n\n# Install dependencies (first time only)\nmake install\n\n# Build and run development server\nmake dev\n```\n\nThe gallery will be available at http://localhost:3000\n\nFor more details, see [crates/story-web/README.md](crates/story-web/README.md).\n\nCheck out [CONTRIBUTING.md](CONTRIBUTING.md) for more details.\n\n## Compare to others\n\n| Features              | GPUI Component                 | [Iced]             | [egui]                | [Qt 6]                                            |\n| --------------------- | ------------------------------ | ------------------ | --------------------- | ------------------------------------------------- |\n| Language              | Rust                           | Rust               | Rust                  | C++/QML                                           |\n| Core Render           | GPUI                           | wgpu               | wgpu                  | QT                                                |\n| License               | Apache 2.0                     | MIT                | MIT/Apache 2.0        | [Commercial/LGPL](https://www.qt.io/qt-licensing) |\n| Min Binary Size [^1]  | 12MB                           | 11MB               | 5M                    | 20MB [^2]                                         |\n| Cross-Platform        | Yes                            | Yes                | Yes                   | Yes                                               |\n| Documentation         | Simple                         | Simple             | Simple                | Good                                              |\n| Web                   | Yes (WASM)                     | Yes                | Yes                   | Yes                                               |\n| UI Style              | Modern                         | Basic              | Basic                 | Basic                                             |\n| CJK Support           | Yes                            | Yes                | Bad                   | Yes                                               |\n| Chart                 | Yes                            | No                 | No                    | Yes                                               |\n| Table (Large dataset) | Yes<br>(Virtual Rows, Columns) | No                 | Yes<br>(Virtual Rows) | Yes<br>(Virtual Rows, Columns)                    |\n| Table Column Resize   | Yes                            | No                 | Yes                   | Yes                                               |\n| Text base             | Rope                           | [COSMIC Text] [^3] | trait TextBuffer [^4] | [QTextDocument]                                   |\n| CodeEditor            | Simple                         | Simple             | Simple                | Basic API                                         |\n| Dock Layout           | Yes                            | Yes                | Yes                   | Yes                                               |\n| Syntax Highlight      | [Tree Sitter]                  | [Syntect]          | [Syntect]             | [QSyntaxHighlighter]                              |\n| Markdown Rendering    | Yes                            | Yes                | Basic                 | No                                                |\n| Markdown mix HTML     | Yes                            | No                 | No                    | No                                                |\n| HTML Rendering        | Basic                          | No                 | No                    | Basic                                             |\n| Text Selection        | TextView                       | No                 | Any Label             | Yes                                               |\n| Custom Theme          | Yes                            | Yes                | Yes                   | Yes                                               |\n| Built Themes          | Yes                            | No                 | No                    | No                                                |\n| I18n                  | Yes                            | Yes                | Yes                   | Yes                                               |\n\n> Please submit an issue or PR if any mistakes or outdated are found.\n\n[Iced]: https://github.com/iced-rs/iced\n[egui]: https://github.com/emilk/egui\n[QT 6]: https://www.qt.io/product/qt6\n[Tree Sitter]: https://tree-sitter.github.io/tree-sitter/\n[Syntect]: https://github.com/trishume/syntect\n[QSyntaxHighlighter]: https://doc.qt.io/qt-6/qsyntaxhighlighter.html\n[QTextDocument]: https://doc.qt.io/qt-6/qtextdocument.html\n[COSMIC Text]: https://github.com/pop-os/cosmic-text\n\n[^1]: Release builds by use simple hello world example.\n\n[^2]: [Reducing Binary Size of Qt Applications](https://www.qt.io/blog/reducing-binary-size-of-qt-applications-part-3-more-platforms)\n\n[^3]: Iced Editor: <https://github.com/iced-rs/iced/blob/db5a1f6353b9f8520c4f9633d1cdc90242c2afe1/graphics/src/text/editor.rs#L65-L68>\n\n[^4]: egui TextBuffer: <https://github.com/emilk/egui/blob/0a81372cfd3a4deda640acdecbbaf24bf78bb6a2/crates/egui/src/widgets/text_edit/text_buffer.rs#L20>\n\n## License\n\nApache-2.0\n\n- UI design based on [shadcn/ui](https://ui.shadcn.com).\n- Icons from [Lucide](https://lucide.dev).\n"
  },
  {
    "path": "crates/assets/Cargo.toml",
    "content": "[package]\nname = \"gpui-component-assets\"\ndescription = \"Default bundled assets for GPUI Component.\"\nkeywords = [\"desktop\", \"gpui\", \"shadcn\", \"ui\", \"uikit\"]\nlicense = \"Apache-2.0\"\ndocumentation = \"https://docs.rs/gpui-component\"\nhomepage = \"https://longbridge.github.io/gpui-component\"\nrepository = \"https://github.com/longbridge/gpui-component\"\nreadme = \"README.md\"\nedition.workspace = true\npublish = true\nversion = \"0.5.1\"\n\n[lib]\ndoctest = false\n\n[dependencies]\nanyhow.workspace = true\ngpui.workspace = true\nlog.workspace = true\n\n[target.'cfg(not(target_family = \"wasm\"))'.dependencies]\nrust-embed = { version = \"8.7.2\", features = [\"interpolate-folder-path\", \"include-exclude\"] }\n\n[target.'cfg(target_family = \"wasm\")'.dependencies]\nreqwest = { workspace = true }\nwasm-bindgen = \"0.2\"\nwasm-bindgen-futures = \"0.4\"\n"
  },
  {
    "path": "crates/assets/README.md",
    "content": "# GPUI Component Assets\n\nThe default assets bundle for [GPUI Component](https://github.com/longbridge/gpui-component).\n\n## License\n\nApache-2.0\n"
  },
  {
    "path": "crates/assets/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "crates/assets/src/lib.rs",
    "content": "/// Embed application assets for GPUI Component.\n///\n/// This assets provides icons svg files for [IconName](https://docs.rs/gpui-component/latest/gpui_component/enum.IconName.html).\n///\n/// ## Usage\n///\n/// ```rust,no_run\n/// use gpui::*;\n/// use gpui_component_assets::Assets;\n///\n/// let app = gpui_platform::application().with_assets(Assets);\n/// ```\n///\n/// ## Platform Differences\n///\n/// - **Native (Desktop)**: Icons are embedded in the binary using RustEmbed\n/// - **WASM (Web)**: Icons are downloaded from CDN using web_sys::Request\n///   - This significantly reduces WASM bundle size\n///   - Icons are downloaded on-demand when first used\n///   - Downloaded icons are cached in memory\n#[cfg(not(target_family = \"wasm\"))]\nmod native_assets;\n\n#[cfg(target_family = \"wasm\")]\nmod wasm_assets;\n\n#[cfg(not(target_family = \"wasm\"))]\npub use native_assets::Assets;\n\n#[cfg(target_family = \"wasm\")]\npub use wasm_assets::Assets;\n"
  },
  {
    "path": "crates/assets/src/native_assets.rs",
    "content": "use anyhow::anyhow;\nuse gpui::{AssetSource, Result, SharedString};\nuse std::borrow::Cow;\n\n/// Native implementation using RustEmbed\n#[derive(rust_embed::RustEmbed)]\n#[folder = \"assets\"]\n#[include = \"icons/**/*.svg\"]\npub struct Assets;\n\nimpl Assets {\n    /// Create a new Assets instance. The endpoint parameter is ignored for native builds.\n    pub fn new(_endpoint: impl Into<SharedString>) -> Self {\n        Self\n    }\n}\n\nimpl AssetSource for Assets {\n    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {\n        if path.is_empty() {\n            return Ok(None);\n        }\n\n        Self::get(path)\n            .map(|f| Some(f.data))\n            .ok_or_else(|| anyhow!(\"could not find asset at path \\\"{}\\\"\", path))\n    }\n\n    fn list(&self, path: &str) -> Result<Vec<SharedString>> {\n        Ok(Self::iter()\n            .filter_map(|p| p.starts_with(path).then(|| p.into()))\n            .collect())\n    }\n}\n"
  },
  {
    "path": "crates/assets/src/wasm_assets.rs",
    "content": "use anyhow::anyhow;\nuse gpui::{AssetSource, Result, SharedString};\nuse std::borrow::Cow;\nuse std::collections::HashMap;\nuse std::sync::{Arc, RwLock};\nuse wasm_bindgen_futures::spawn_local;\n\n/// WASM implementation - download assets on-demand\npub struct Assets {\n    endpoint: SharedString,\n    cache: Arc<RwLock<HashMap<String, Vec<u8>>>>,\n    pending: Arc<RwLock<HashMap<String, bool>>>,\n}\n\nimpl Assets {\n    pub fn new(endpoint: impl Into<SharedString>) -> Self {\n        Self {\n            endpoint: endpoint.into(),\n            cache: Arc::new(RwLock::new(HashMap::new())),\n            pending: Arc::new(RwLock::new(HashMap::new())),\n        }\n    }\n}\n\nimpl Default for Assets {\n    fn default() -> Self {\n        Self::new(\"\")\n    }\n}\n\nimpl AssetSource for Assets {\n    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {\n        if path.is_empty() {\n            return Ok(None);\n        }\n\n        if path.starts_with(\"icons/\") && path.ends_with(\".svg\") {\n            // Check if already cached\n            if let Ok(cache) = self.cache.read() {\n                if let Some(data) = cache.get(path) {\n                    return Ok(Some(Cow::Owned(data.clone())));\n                }\n            }\n\n            // Check if download is already pending\n            let is_pending = self\n                .pending\n                .read()\n                .map(|p| p.contains_key(path))\n                .unwrap_or(false);\n\n            if !is_pending {\n                // Mark as pending and start download\n                if let Ok(mut pending) = self.pending.write() {\n                    pending.insert(path.to_string(), true);\n                }\n\n                let url = format!(\"{}/assets/{}\", self.endpoint, path);\n                let path_clone = path.to_string();\n                let cache = self.cache.clone();\n                let pending = self.pending.clone();\n\n                spawn_local(async move {\n                    match reqwest::get(&url).await {\n                        Ok(response) => {\n                            if response.status().is_success() {\n                                match response.bytes().await {\n                                    Ok(bytes) => {\n                                        if let Ok(mut cache) = cache.write() {\n                                            cache.insert(path_clone.clone(), bytes.to_vec());\n                                        }\n                                    }\n                                    Err(e) => {\n                                        log::warn!(\"Failed to read icon {}: {}\", path_clone, e);\n                                    }\n                                }\n                            } else {\n                                log::warn!(\n                                    \"Failed to download icon {}: HTTP {}\",\n                                    path_clone,\n                                    response.status()\n                                );\n                            }\n                        }\n                        Err(e) => {\n                            log::warn!(\"Failed to fetch icon {}: {}\", path_clone, e);\n                        }\n                    }\n\n                    // Remove from pending\n                    if let Ok(mut pending) = pending.write() {\n                        pending.remove(&path_clone);\n                    }\n                });\n            }\n\n            // Return error so GPUI will retry (but only log once per icon)\n            Err(anyhow!(\"Wasm assets loading, will be available soon...\"))\n        } else {\n            Ok(None)\n        }\n    }\n\n    fn list(&self, path: &str) -> Result<Vec<SharedString>> {\n        let _ = path;\n        Ok(Vec::new())\n    }\n}\n"
  },
  {
    "path": "crates/macros/Cargo.toml",
    "content": "[package]\nname = \"gpui-component-macros\"\ndescription = \"Macros for GPUI Component.\"\nversion = \"0.5.1\"\nlicense = \"Apache-2.0\"\npublish = true\nedition.workspace = true\n\n[lib]\nproc-macro = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nproc-macro2 = \"1.0\"\nquote = \"1.0\"\nsyn = \"2.0\"\n\n[package.metadata.cargo-machete]\nignored = [\"proc-macro2\", \"core-text\"]\n"
  },
  {
    "path": "crates/macros/src/derive_into_plot.rs",
    "content": "use proc_macro::TokenStream;\nuse quote::quote;\nuse syn::{parse_macro_input, DeriveInput};\n\npub fn derive_into_plot(input: TokenStream) -> TokenStream {\n    let ast = parse_macro_input!(input as DeriveInput);\n    let type_name = &ast.ident;\n    let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl();\n\n    let expanded = quote! {\n        impl #impl_generics gpui::IntoElement for #type_name #type_generics #where_clause {\n            type Element = Self;\n\n            fn into_element(self) -> Self::Element {\n                self\n            }\n        }\n\n        impl #impl_generics gpui::Element for #type_name #type_generics #where_clause {\n            type RequestLayoutState = ();\n            type PrepaintState = ();\n\n            fn id(&self) -> Option<gpui::ElementId> {\n                None\n            }\n\n            fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n                None\n            }\n\n            fn request_layout(\n                &mut self,\n                _: Option<&gpui::GlobalElementId>,\n                _: Option<&gpui::InspectorElementId>,\n                window: &mut gpui::Window,\n                cx: &mut gpui::App,\n            ) -> (gpui::LayoutId, Self::RequestLayoutState) {\n                let style = gpui::Style {\n                    size: gpui::Size::full(),\n                    ..Default::default()\n                };\n\n                (window.request_layout(style, None, cx), ())\n            }\n\n            fn prepaint(\n                &mut self,\n                _: Option<&gpui::GlobalElementId>,\n                _: Option<&gpui::InspectorElementId>,\n                _: gpui::Bounds<gpui::Pixels>,\n                _: &mut Self::RequestLayoutState,\n                _: &mut gpui::Window,\n                _: &mut gpui::App,\n            ) -> Self::PrepaintState {\n            }\n\n            fn paint(\n                &mut self,\n                _: Option<&gpui::GlobalElementId>,\n                _: Option<&gpui::InspectorElementId>,\n                bounds: gpui::Bounds<gpui::Pixels>,\n                _: &mut Self::RequestLayoutState,\n                _: &mut Self::PrepaintState,\n                window: &mut gpui::Window,\n                cx: &mut gpui::App,\n            ) {\n                <Self as Plot>::paint(self, bounds, window, cx)\n            }\n        }\n    };\n\n    TokenStream::from(expanded)\n}\n"
  },
  {
    "path": "crates/macros/src/lib.rs",
    "content": "use proc_macro::TokenStream;\nuse quote::quote;\nuse syn::parse::{Parse, ParseStream};\n\nmod derive_into_plot;\n\n/// Input for icon_name! macro: EnumName, \"path\", [optional derives]\nstruct IconNameInput {\n    enum_name: syn::Ident,\n    _comma: syn::Token![,],\n    path: syn::LitStr,\n    derives: Option<(\n        syn::Token![,],\n        syn::punctuated::Punctuated<syn::Path, syn::Token![,]>,\n    )>,\n}\n\nimpl Parse for IconNameInput {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        let enum_name = input.parse()?;\n        let _comma = input.parse()?;\n        let path = input.parse()?;\n\n        // Check if there's an optional derives list\n        let derives = if input.peek(syn::Token![,]) {\n            let comma = input.parse()?;\n            let content;\n            syn::bracketed!(content in input);\n            let derives = content.parse_terminated(syn::Path::parse, syn::Token![,])?;\n            Some((comma, derives))\n        } else {\n            None\n        };\n\n        Ok(IconNameInput {\n            enum_name,\n            _comma,\n            path,\n            derives,\n        })\n    }\n}\n\n#[proc_macro_derive(IntoPlot)]\npub fn derive_into_plot(input: TokenStream) -> TokenStream {\n    derive_into_plot::derive_into_plot(input)\n}\n\n/// Convert an SVG filename to PascalCase identifier.\n///\n/// Strips `.svg` extension, splits on separators (`-`, `_`, `.`),\n/// and capitalizes each word following Rust naming conventions.\n///\n/// # Examples\n///\n/// ```ignore\n/// assert_eq!(pascal_case(\"arrow-right.svg\"), \"ArrowRight\");\n/// assert_eq!(pascal_case(\"some_icon_name.svg\"), \"SomeIconName\");\n/// assert_eq!(pascal_case(\"icon-123.svg\"), \"Icon123\");\n/// ```\nfn pascal_case(filename: &str) -> String {\n    filename\n        .strip_suffix(\".svg\")\n        .unwrap_or(filename)\n        .split(|c: char| c == '-' || c == '_' || c == '.')\n        .filter(|part| !part.is_empty())\n        .map(|word| {\n            let mut chars = word.chars();\n            match chars.next() {\n                None => String::new(),\n                Some(first) if first.is_ascii_digit() => word.to_string(),\n                Some(first) => {\n                    let mut result = String::with_capacity(word.len());\n                    result.extend(first.to_uppercase());\n                    result.push_str(&chars.as_str().to_lowercase());\n                    result\n                }\n            }\n        })\n        .collect()\n}\n\n/// Generate a custom icon enum and its `IconNamed` impl by scanning a directory of SVG files.\n///\n/// Accepts an enum name, a path relative to the calling crate's `CARGO_MANIFEST_DIR`,\n/// and optionally a list of additional derive traits.\n///\n/// # Example\n///\n/// ```ignore\n/// // Basic usage (derives IntoElement, Clone by default)\n/// icon_named!(IconName, \"../assets/assets/icons\");\n///\n/// // With custom derives\n/// icon_named!(IconName, \"../assets/assets/icons\", [Debug, Copy, PartialEq, Eq]);\n/// ```\n#[proc_macro]\npub fn icon_named(input: TokenStream) -> TokenStream {\n    let IconNameInput {\n        enum_name,\n        path,\n        derives,\n        ..\n    } = syn::parse_macro_input!(input as IconNameInput);\n\n    let relative_path = path.value();\n\n    let manifest_dir = std::env::var(\"CARGO_MANIFEST_DIR\").expect(\"CARGO_MANIFEST_DIR not set\");\n    let icons_dir = std::path::Path::new(&manifest_dir).join(&relative_path);\n\n    let mut entries: Vec<(String, String)> = Vec::new();\n\n    let dir = std::fs::read_dir(&icons_dir).unwrap_or_else(|e| {\n        panic!(\n            \"generate_icon_enum: failed to read '{}': {}\",\n            icons_dir.display(),\n            e\n        )\n    });\n\n    for entry in dir {\n        let entry = entry.expect(\"failed to read directory entry\");\n        let filename = entry.file_name().to_string_lossy().to_string();\n        if filename.ends_with(\".svg\") {\n            let variant_name = pascal_case(&filename);\n            let path = format!(\"icons/{}\", filename);\n            entries.push((variant_name, path));\n        }\n    }\n\n    entries.sort_by(|a, b| a.0.cmp(&b.0));\n\n    let variants: Vec<proc_macro2::Ident> = entries\n        .iter()\n        .map(|(name, _)| proc_macro2::Ident::new(name, proc_macro2::Span::call_site()))\n        .collect();\n    let paths: Vec<&str> = entries.iter().map(|(_, p)| p.as_str()).collect();\n\n    // Build derive list: always include IntoElement and Clone, then add custom derives\n    let derive_attrs = if let Some((_, custom_derives)) = derives {\n        let derives_vec: Vec<_> = custom_derives.iter().collect();\n        quote! {\n            #[derive(IntoElement, Clone, #(#derives_vec),*)]\n        }\n    } else {\n        quote! {\n            #[derive(IntoElement, Clone)]\n        }\n    };\n\n    let expanded = quote! {\n        #derive_attrs\n        pub enum #enum_name {\n            #(#variants,)*\n        }\n\n        impl IconNamed for #enum_name {\n            fn path(self) -> SharedString {\n                match self {\n                    #(Self::#variants => #paths,)*\n                }\n                .into()\n            }\n        }\n    };\n\n    TokenStream::from(expanded)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_pascal_case_basic() {\n        assert_eq!(pascal_case(\"arrow-right.svg\"), \"ArrowRight\");\n        assert_eq!(pascal_case(\"home.svg\"), \"Home\");\n        assert_eq!(pascal_case(\"x-circle.svg\"), \"XCircle\");\n\n        assert_eq!(pascal_case(\"some_icon_name.svg\"), \"SomeIconName\");\n        assert_eq!(pascal_case(\"arrow_up_down.svg\"), \"ArrowUpDown\");\n\n        assert_eq!(pascal_case(\"kebab-case_mixed.svg\"), \"KebabCaseMixed\");\n        assert_eq!(pascal_case(\"icon-with_under.svg\"), \"IconWithUnder\");\n\n        assert_eq!(pascal_case(\"icon-123.svg\"), \"Icon123\");\n        assert_eq!(pascal_case(\"arrow-2x.svg\"), \"Arrow2x\");\n        assert_eq!(pascal_case(\"24-hour.svg\"), \"24Hour\");\n\n        assert_eq!(pascal_case(\"arrow--right.svg\"), \"ArrowRight\");\n        assert_eq!(pascal_case(\"icon__name.svg\"), \"IconName\");\n        assert_eq!(pascal_case(\"multiple---dash.svg\"), \"MultipleDash\");\n\n        assert_eq!(pascal_case(\"a.svg\"), \"A\");\n        assert_eq!(pascal_case(\"-leading.svg\"), \"Leading\");\n        assert_eq!(pascal_case(\"trailing-.svg\"), \"Trailing\");\n        assert_eq!(pascal_case(\"-.svg\"), \"\");\n\n        assert_eq!(pascal_case(\"arrow-right\"), \"ArrowRight\");\n        assert_eq!(pascal_case(\"home\"), \"Home\");\n\n        assert_eq!(pascal_case(\"hello.svg\"), \"Hello\");\n        assert_eq!(pascal_case(\"WORLD.svg\"), \"World\");\n        assert_eq!(pascal_case(\"iOS-icon.svg\"), \"IosIcon\");\n        assert_eq!(pascal_case(\"API-key.svg\"), \"ApiKey\");\n    }\n}\n"
  },
  {
    "path": "crates/story/Cargo.toml",
    "content": "[package]\nname = \"gpui-component-story\"\nversion = \"0.5.1\"\npublish = false\nedition.workspace = true\n\n[features]\ninspector = [\"gpui-component/inspector\"]\ntree-sitter = [\"gpui-component/tree-sitter-languages\"]\ndefault = [\"tree-sitter\"]\n\n[dependencies]\nanyhow.workspace = true\ngpui.workspace = true\ngpui_platform.workspace = true\ngpui-component = { workspace = true }\ngpui-component-assets.workspace = true\n\nautocorrect = \"2.14.2\"\nchrono = \"0.4\"\ncsv = \"1.4\"\nfake = { version = \"2.10.0\", features = [\"dummy\"] }\nitertools = \"0.14.0\"\nlsp-types.workspace = true\nrand = \"0.8\"\nregex = \"1\"\nserde = \"1\"\nserde_json = \"1\"\ntracing.workspace = true\ntracing-subscriber = { version = \"0.3.22\", features = [\"env-filter\", \"fmt\", \"registry\"], default-features = false }\nunindent = \"0.2.3\"\nrust-i18n.workspace = true\ndirs = \"6.0.0\"\n\n# Native-only dependencies (not available on WASM)\n[target.'cfg(not(target_family = \"wasm\"))'.dependencies]\nreqwest_client.workspace = true\nsmol.workspace = true\ntree-sitter-navi = \"0.2.2\"\ncolor-lsp = \"0.2.0\"\n\n[target.'cfg(target_family = \"wasm\")'.dependencies]\ngpui_web.workspace = true\n\n[target.'cfg(target_os = \"linux\")'.dependencies]\ngtk = { version = \"0.18\" }\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "crates/story/examples/brush.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{StyleRefinement, prelude::FluentBuilder, *};\nuse gpui_component::{\n    ActiveTheme, Colorize as _, ElementExt, IconName, Sizable,\n    button::Button,\n    checkbox::Checkbox,\n    group_box::{GroupBox, GroupBoxVariants as _},\n    h_flex,\n    slider::{Slider, SliderState},\n    v_flex,\n};\nuse gpui_component_assets::Assets;\n\npub struct BrushStory {\n    focus_handle: gpui::FocusHandle,\n    brush_size: Entity<SliderState>,\n    brush_opacity: Entity<SliderState>,\n    brush_color: Hsla,\n    strokes: Rc<Vec<Stroke>>,\n    current_stroke: Option<Stroke>,\n    is_drawing: bool,\n    show_grid: bool,\n    canvas_bounds: Option<Bounds<Pixels>>,\n}\n\n#[derive(Clone, Debug)]\nstruct Stroke {\n    points: Vec<Point<Pixels>>,\n    color: Hsla,\n    size: f32,\n}\n\nimpl BrushStory {\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        let brush_size = cx.new(|_| {\n            SliderState::new()\n                .min(1.)\n                .max(50.)\n                .default_value(5.)\n                .step(1.)\n        });\n\n        let brush_opacity = cx.new(|_| {\n            SliderState::new()\n                .min(0.1)\n                .max(1.0)\n                .default_value(1.0)\n                .step(0.05)\n        });\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            brush_size,\n            brush_opacity,\n            brush_color: black(),\n            strokes: Rc::new(vec![]),\n            current_stroke: None,\n            is_drawing: false,\n            show_grid: false,\n            canvas_bounds: None,\n        }\n    }\n\n    fn handle_mouse_down(\n        &mut self,\n        event: &MouseDownEvent,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if event.button == MouseButton::Left {\n            self.is_drawing = true;\n            let brush_size = self.brush_size.read(cx).value().start();\n            let brush_opacity = self.brush_opacity.read(cx).value().start();\n            let color = self.brush_color.opacity(brush_opacity);\n\n            let local_pos = if let Some(bounds) = self.canvas_bounds {\n                Point::new(\n                    event.position.x - bounds.origin.x,\n                    event.position.y - bounds.origin.y,\n                )\n            } else {\n                event.position\n            };\n\n            self.current_stroke = Some(Stroke {\n                points: vec![local_pos],\n                color,\n                size: brush_size,\n            });\n            cx.notify();\n        }\n    }\n\n    fn handle_mouse_move(\n        &mut self,\n        event: &MouseMoveEvent,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if self.is_drawing {\n            if let Some(ref mut stroke) = self.current_stroke {\n                let local_pos = if let Some(bounds) = self.canvas_bounds {\n                    Point::new(\n                        event.position.x - bounds.origin.x,\n                        event.position.y - bounds.origin.y,\n                    )\n                } else {\n                    event.position\n                };\n\n                let should_add = if let Some(last) = stroke.points.last() {\n                    let dx_px = local_pos.x - last.x;\n                    let dy_px = local_pos.y - last.y;\n\n                    let dx_abs = if dx_px < px(0.0) {\n                        px(0.0) - dx_px\n                    } else {\n                        dx_px\n                    };\n                    let dy_abs = if dy_px < px(0.0) {\n                        px(0.0) - dy_px\n                    } else {\n                        dy_px\n                    };\n                    dx_abs >= px(1.0) || dy_abs >= px(1.0)\n                } else {\n                    true\n                };\n\n                if should_add {\n                    stroke.points.push(local_pos);\n                    cx.notify();\n                }\n            }\n        }\n    }\n\n    fn handle_mouse_up(&mut self, _event: &MouseUpEvent, _: &mut Window, cx: &mut Context<Self>) {\n        if self.is_drawing {\n            self.is_drawing = false;\n            if let Some(stroke) = self.current_stroke.take() {\n                if stroke.points.len() > 1 {\n                    let mut new_strokes = (*self.strokes).clone();\n                    new_strokes.push(stroke);\n                    self.strokes = Rc::new(new_strokes);\n                }\n            }\n            cx.notify();\n        }\n    }\n\n    fn clear_canvas(&mut self, cx: &mut Context<Self>) {\n        self.strokes = Rc::new(vec![]);\n        self.current_stroke = None;\n        self.is_drawing = false;\n        cx.notify();\n    }\n\n    fn set_brush_color(&mut self, color: Hsla, cx: &mut Context<Self>) {\n        self.brush_color = color;\n        cx.notify();\n    }\n}\n\nimpl Focusable for BrushStory {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl BrushStory {\n    fn color_button(&self, color: Hsla, _label: &str, cx: &Context<Self>) -> impl IntoElement {\n        let is_selected = self.brush_color.to_hex() == color.to_hex();\n        let theme = cx.theme();\n\n        div()\n            .w(px(40.))\n            .h(px(40.))\n            .rounded(theme.radius)\n            .bg(color)\n            .border_2()\n            .when(is_selected, |this| {\n                this.border_color(theme.primary).shadow_md()\n            })\n            .when(!is_selected, |this| this.border_color(theme.border))\n            .cursor_pointer()\n            .on_mouse_down(\n                MouseButton::Left,\n                cx.listener(move |this, _, _, cx| {\n                    this.set_brush_color(color, cx);\n                }),\n            )\n    }\n\n    fn render_canvas(&mut self, cx: &Context<Self>) -> impl IntoElement {\n        let theme = cx.theme();\n\n        let strokes_for_prepaint = self.strokes.clone();\n        let current_stroke_for_prepaint = self.current_stroke.clone();\n        let show_grid_for_prepaint = self.show_grid;\n        let theme_for_prepaint = theme.clone();\n\n        let state_entity = cx.entity().clone();\n\n        let base_div = div()\n            .id(\"canvas\")\n            .size_full()\n            .bg(theme.background)\n            .cursor_crosshair()\n            .relative()\n            .on_mouse_down(MouseButton::Left, cx.listener(Self::handle_mouse_down))\n            .on_mouse_move(cx.listener(Self::handle_mouse_move))\n            .on_mouse_up(MouseButton::Left, cx.listener(Self::handle_mouse_up))\n            .on_prepaint(move |bounds, _window, cx| {\n                state_entity.update(cx, |state, _| {\n                    state.canvas_bounds = Some(bounds);\n                })\n            });\n\n        base_div.child(\n            canvas(\n                move |bounds, _window, _cx| {\n                    (\n                        strokes_for_prepaint,\n                        current_stroke_for_prepaint,\n                        show_grid_for_prepaint,\n                        theme_for_prepaint,\n                        bounds,\n                    )\n                },\n                move |_bounds,\n                      (strokes, current_stroke, show_grid, theme, prepaint_bounds),\n                      window,\n                      _cx| {\n                    let origin = prepaint_bounds.origin;\n                    let size = prepaint_bounds.size;\n\n                    if show_grid {\n                        let grid_color = theme.border.opacity(0.2);\n                        let grid_size = 40.0;\n\n                        let mut x = 0.0;\n                        while px(x) <= size.width {\n                            let mut builder = PathBuilder::stroke(px(1.0));\n                            builder.move_to(Point::new(origin.x + px(x), origin.y));\n                            builder.line_to(Point::new(origin.x + px(x), origin.y + size.height));\n                            if let Ok(path) = builder.build() {\n                                window.paint_path(path, grid_color);\n                            }\n                            x += grid_size;\n                        }\n\n                        let mut y = 0.0;\n                        while px(y) <= size.height {\n                            let mut builder = PathBuilder::stroke(px(1.0));\n                            builder.move_to(Point::new(origin.x, origin.y + px(y)));\n                            builder.line_to(Point::new(origin.x + size.width, origin.y + px(y)));\n                            if let Ok(path) = builder.build() {\n                                window.paint_path(path, grid_color);\n                            }\n                            y += grid_size;\n                        }\n                    }\n\n                    for stroke in strokes.iter() {\n                        if let Some(path) = BrushStory::build_stroke_path(stroke, &prepaint_bounds)\n                        {\n                            window.paint_path(path, stroke.color);\n                        }\n                    }\n\n                    if let Some(ref stroke) = current_stroke {\n                        if let Some(path) = BrushStory::build_stroke_path(stroke, &prepaint_bounds)\n                        {\n                            window.paint_path(path, stroke.color);\n                        }\n                    }\n                },\n            )\n            .absolute()\n            .size_full(),\n        )\n    }\n\n    fn build_stroke_path(stroke: &Stroke, bounds: &Bounds<Pixels>) -> Option<Path<Pixels>> {\n        if stroke.points.len() < 2 {\n            return None;\n        }\n\n        let mut builder = PathBuilder::stroke(px(stroke.size));\n\n        let first_point = Point::new(\n            bounds.origin.x + stroke.points[0].x,\n            bounds.origin.y + stroke.points[0].y,\n        );\n        builder.move_to(first_point);\n\n        for point in stroke.points.iter().skip(1) {\n            let abs_point = Point::new(bounds.origin.x + point.x, bounds.origin.y + point.y);\n            builder.line_to(abs_point);\n        }\n\n        builder.build().ok()\n    }\n}\n\nimpl Render for BrushStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let theme = cx.theme();\n        let brush_size = self.brush_size.read(cx).value().start();\n        let brush_opacity = self.brush_opacity.read(cx).value().start();\n\n        v_flex()\n            .size_full()\n            .gap_6()\n            .child(\n                section(\"Controls\").child(\n                    h_flex()\n                        .gap_8()\n                        .w_full()\n                        .items_start()\n                        .child(\n                            v_flex()\n                                .gap_4()\n                                .flex_1()\n                                .w(relative(0.5))\n                                .child(\n                                    h_flex()\n                                        .gap_4()\n                                        .items_center()\n                                        .child(\"Size:\")\n                                        .child(\n                                            Slider::new(&self.brush_size)\n                                                .w(px(200.))\n                                                .bg(theme.primary)\n                                                .text_color(theme.primary_foreground),\n                                        )\n                                        .child(format!(\"{:.0}px\", brush_size)),\n                                )\n                                .child(\n                                    h_flex()\n                                        .gap_4()\n                                        .items_center()\n                                        .child(\"Opacity:\")\n                                        .child(\n                                            Slider::new(&self.brush_opacity)\n                                                .w(px(200.))\n                                                .bg(theme.primary)\n                                                .text_color(theme.primary_foreground),\n                                        )\n                                        .child(format!(\"{:.0}%\", brush_opacity * 100.0)),\n                                )\n                                .child(\n                                    h_flex()\n                                        .gap_3()\n                                        .items_center()\n                                        .child(\n                                            Button::new(\"clear-canvas\")\n                                                .icon(IconName::Close)\n                                                .label(\"Clear Canvas\")\n                                                .small()\n                                                .on_click(cx.listener(|this, _, _, cx| {\n                                                    this.clear_canvas(cx);\n                                                })),\n                                        )\n                                        .child(\n                                            Checkbox::new(\"show-grid\")\n                                                .label(\"Show Grid\")\n                                                .checked(self.show_grid)\n                                                .on_click(cx.listener(|this, checked, _, cx| {\n                                                    this.show_grid = *checked;\n                                                    cx.notify();\n                                                })),\n                                        ),\n                                ),\n                        )\n                        .child(\n                            v_flex()\n                                .gap_2()\n                                .flex_1()\n                                .w(relative(0.5))\n                                .child(h_flex().gap_2().items_center().child(\"Color:\"))\n                                .child(\n                                    h_flex()\n                                        .gap_3()\n                                        .flex_wrap()\n                                        .child(self.color_button(black(), \"Black\", cx))\n                                        .child(self.color_button(white(), \"White\", cx))\n                                        .child(self.color_button(red(), \"Red\", cx))\n                                        .child(self.color_button(green(), \"Green\", cx))\n                                        .child(self.color_button(blue(), \"Blue\", cx))\n                                        .child(self.color_button(yellow(), \"Yellow\", cx))\n                                        .child(self.color_button(\n                                            hsla(0.58, 1.0, 0.5, 1.0),\n                                            \"Purple\",\n                                            cx,\n                                        ))\n                                        .child(self.color_button(\n                                            hsla(0.083, 1.0, 0.5, 1.0),\n                                            \"Orange\",\n                                            cx,\n                                        )),\n                                ),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Drawing Canvas\")\n                    .child(self.render_canvas(cx))\n                    .flex_1(),\n            )\n    }\n}\n\nfn section(title: impl Into<SharedString>) -> GroupBox {\n    GroupBox::new()\n        .outline()\n        .title(\n            h_flex()\n                .justify_between()\n                .w_full()\n                .gap_4()\n                .child(title.into()),\n        )\n        .content_style(StyleRefinement::default().flex_1().size_full())\n}\n\npub struct Example {\n    root: Entity<BrushStory>,\n}\n\nimpl Example {\n    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let root = cx.new(|cx| BrushStory::new(window, cx));\n        Self { root }\n    }\n\n    fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\n\nimpl Render for Example {\n    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {\n        div().p_4().size_full().child(self.root.clone())\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application().with_assets(Assets);\n\n    app.run(move |cx| {\n        gpui_component_story::init(cx);\n        cx.activate(true);\n\n        gpui_component_story::create_new_window(\"Brush Example\", Example::view, cx);\n    });\n}\n"
  },
  {
    "path": "crates/story/examples/dock.rs",
    "content": "use anyhow::{Context as _, Result};\nuse gpui::*;\nuse gpui_component::{\n    IconName, Root, Sizable,\n    button::{Button, ButtonVariants as _},\n    dock::{ClosePanel, DockArea, DockAreaState, DockEvent, DockItem, DockPlacement, ToggleZoom},\n    menu::DropdownMenu,\n};\n\nuse gpui_component_assets::Assets;\nuse gpui_component_story::{\n    AccordionStory, AppState, AppTitleBar, ButtonStory, CalendarStory, DialogStory, FormStory,\n    IconStory, ImageStory, InputStory, LabelStory, ListStory, NotificationStory, Open,\n    PopoverStory, ProgressStory, ResizableStory, ScrollbarStory, SelectStory, SidebarStory,\n    StoryContainer, SwitchStory, DataTableStory, TooltipStory,\n};\nuse serde::Deserialize;\nuse std::{sync::Arc, time::Duration};\n\n#[derive(Action, Clone, PartialEq, Eq, Deserialize)]\n#[action(namespace = story, no_json)]\npub struct AddPanel(DockPlacement);\n\n#[derive(Action, Clone, PartialEq, Eq, Deserialize)]\n#[action(namespace = story, no_json)]\npub struct TogglePanelVisible(SharedString);\n\nactions!(story, [ToggleDockToggleButton]);\n\nconst MAIN_DOCK_AREA: DockAreaTab = DockAreaTab {\n    id: \"main-dock\",\n    version: 5,\n};\n\n#[cfg(debug_assertions)]\nconst STATE_FILE: &str = \"target/docks.json\";\n#[cfg(not(debug_assertions))]\nconst STATE_FILE: &str = \"docks.json\";\n\npub fn init(cx: &mut App) {\n    cx.on_action(|_action: &Open, _cx: &mut App| {});\n    gpui_component_story::init(cx);\n\n    cx.bind_keys(vec![\n        KeyBinding::new(\"shift-escape\", ToggleZoom, None),\n        KeyBinding::new(\"ctrl-w\", ClosePanel, None),\n    ]);\n\n    cx.activate(true);\n}\n\npub struct StoryWorkspace {\n    title_bar: Entity<AppTitleBar>,\n    dock_area: Entity<DockArea>,\n    last_layout_state: Option<DockAreaState>,\n    toggle_button_visible: bool,\n    _save_layout_task: Option<Task<()>>,\n}\n\nstruct DockAreaTab {\n    id: &'static str,\n    version: usize,\n}\n\nimpl StoryWorkspace {\n    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let dock_area =\n            cx.new(|cx| DockArea::new(MAIN_DOCK_AREA.id, Some(MAIN_DOCK_AREA.version), window, cx));\n        let weak_dock_area = dock_area.downgrade();\n\n        match Self::load_layout(dock_area.clone(), window, cx) {\n            Ok(_) => {\n                println!(\"load layout success\");\n            }\n            Err(err) => {\n                eprintln!(\"load layout error: {:?}\", err);\n                Self::reset_default_layout(weak_dock_area, window, cx);\n            }\n        };\n\n        cx.subscribe_in(\n            &dock_area,\n            window,\n            |this, dock_area, ev: &DockEvent, window, cx| match ev {\n                DockEvent::LayoutChanged => this.save_layout(dock_area, window, cx),\n                _ => {}\n            },\n        )\n        .detach();\n\n        cx.on_app_quit({\n            let dock_area = dock_area.clone();\n            move |_, cx| {\n                let state = dock_area.read(cx).dump(cx);\n                cx.background_executor().spawn(async move {\n                    // Save layout before quitting\n                    Self::save_state(&state).unwrap();\n                })\n            }\n        })\n        .detach();\n\n        let title_bar = cx.new(|cx| {\n            AppTitleBar::new(\"Examples\", window, cx).child({\n                move |_, cx| {\n                    Button::new(\"add-panel\")\n                        .icon(IconName::LayoutDashboard)\n                        .small()\n                        .ghost()\n                        .dropdown_menu({\n                            let invisible_panels = AppState::global(cx).invisible_panels.clone();\n\n                            move |menu, _, cx| {\n                                menu.menu(\n                                    \"Add Panel to Center\",\n                                    Box::new(AddPanel(DockPlacement::Center)),\n                                )\n                                .separator()\n                                .menu(\"Add Panel to Left\", Box::new(AddPanel(DockPlacement::Left)))\n                                .menu(\n                                    \"Add Panel to Right\",\n                                    Box::new(AddPanel(DockPlacement::Right)),\n                                )\n                                .menu(\n                                    \"Add Panel to Bottom\",\n                                    Box::new(AddPanel(DockPlacement::Bottom)),\n                                )\n                                .separator()\n                                .menu(\n                                    \"Show / Hide Dock Toggle Button\",\n                                    Box::new(ToggleDockToggleButton),\n                                )\n                                .separator()\n                                .menu_with_check(\n                                    \"Sidebar\",\n                                    !invisible_panels\n                                        .read(cx)\n                                        .contains(&SharedString::from(\"Sidebar\")),\n                                    Box::new(TogglePanelVisible(SharedString::from(\"Sidebar\"))),\n                                )\n                                .menu_with_check(\n                                    \"Dialog\",\n                                    !invisible_panels\n                                        .read(cx)\n                                        .contains(&SharedString::from(\"Dialog\")),\n                                    Box::new(TogglePanelVisible(SharedString::from(\"Dialog\"))),\n                                )\n                                .menu_with_check(\n                                    \"Accordion\",\n                                    !invisible_panels\n                                        .read(cx)\n                                        .contains(&SharedString::from(\"Accordion\")),\n                                    Box::new(TogglePanelVisible(SharedString::from(\"Accordion\"))),\n                                )\n                                .menu_with_check(\n                                    \"List\",\n                                    !invisible_panels\n                                        .read(cx)\n                                        .contains(&SharedString::from(\"List\")),\n                                    Box::new(TogglePanelVisible(SharedString::from(\"List\"))),\n                                )\n                            }\n                        })\n                        .anchor(Corner::TopRight)\n                }\n            })\n        });\n\n        Self {\n            dock_area,\n            title_bar,\n            last_layout_state: None,\n            toggle_button_visible: true,\n            _save_layout_task: None,\n        }\n    }\n\n    fn save_layout(\n        &mut self,\n        dock_area: &Entity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let dock_area = dock_area.clone();\n        self._save_layout_task = Some(cx.spawn_in(window, async move |story, window| {\n            window\n                .background_executor()\n                .timer(Duration::from_secs(10))\n                .await;\n\n            _ = story.update_in(window, move |this, _, cx| {\n                let dock_area = dock_area.read(cx);\n                let state = dock_area.dump(cx);\n\n                let last_layout_state = this.last_layout_state.clone();\n                if Some(&state) == last_layout_state.as_ref() {\n                    return;\n                }\n\n                Self::save_state(&state).unwrap();\n                this.last_layout_state = Some(state);\n            });\n        }));\n    }\n\n    fn save_state(state: &DockAreaState) -> Result<()> {\n        println!(\"Save layout...\");\n        let json = serde_json::to_string_pretty(state)?;\n        std::fs::write(STATE_FILE, json)?;\n        Ok(())\n    }\n\n    fn load_layout(\n        dock_area: Entity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Result<()> {\n        let json = std::fs::read_to_string(STATE_FILE)?;\n        let state = serde_json::from_str::<DockAreaState>(&json)?;\n\n        // Check if the saved layout version is different from the current version\n        // Notify the user and ask if they want to reset the layout to default.\n        if state.version != Some(MAIN_DOCK_AREA.version) {\n            let answer = window.prompt(\n                PromptLevel::Info,\n                \"The default main layout has been updated.\\n\\\n                Do you want to reset the layout to default?\",\n                None,\n                &[\"Yes\", \"No\"],\n                cx,\n            );\n\n            let weak_dock_area = dock_area.downgrade();\n            cx.spawn_in(window, async move |this, window| {\n                if answer.await == Ok(0) {\n                    _ = this.update_in(window, |_, window, cx| {\n                        Self::reset_default_layout(weak_dock_area, window, cx);\n                    });\n                }\n            })\n            .detach();\n        }\n\n        dock_area.update(cx, |dock_area, cx| {\n            dock_area.load(state, window, cx).context(\"load layout\")?;\n            dock_area.set_dock_collapsible(\n                Edges {\n                    left: true,\n                    bottom: true,\n                    right: true,\n                    ..Default::default()\n                },\n                window,\n                cx,\n            );\n\n            Ok::<(), anyhow::Error>(())\n        })\n    }\n\n    fn reset_default_layout(dock_area: WeakEntity<DockArea>, window: &mut Window, cx: &mut App) {\n        let dock_item = Self::init_default_layout(&dock_area, window, cx);\n\n        let left_panels = DockItem::v_split(\n            vec![\n                DockItem::tab(\n                    StoryContainer::panel::<ListStory>(window, cx),\n                    &dock_area,\n                    window,\n                    cx,\n                ),\n                DockItem::tabs(\n                    vec![\n                        Arc::new(StoryContainer::panel::<ScrollbarStory>(window, cx)),\n                        Arc::new(StoryContainer::panel::<AccordionStory>(window, cx)),\n                    ],\n                    &dock_area,\n                    window,\n                    cx,\n                )\n                .size(px(360.)),\n            ],\n            &dock_area,\n            window,\n            cx,\n        );\n\n        let bottom_panels = DockItem::v_split(\n            vec![DockItem::tabs(\n                vec![\n                    Arc::new(StoryContainer::panel::<TooltipStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<IconStory>(window, cx)),\n                ],\n                &dock_area,\n                window,\n                cx,\n            )],\n            &dock_area,\n            window,\n            cx,\n        );\n\n        let right_panels = DockItem::v_split(\n            vec![\n                DockItem::tab(\n                    StoryContainer::panel::<ImageStory>(window, cx),\n                    &dock_area,\n                    window,\n                    cx,\n                ),\n                DockItem::tab(\n                    StoryContainer::panel::<IconStory>(window, cx),\n                    &dock_area,\n                    window,\n                    cx,\n                ),\n            ],\n            &dock_area,\n            window,\n            cx,\n        );\n\n        _ = dock_area.update(cx, |view, cx| {\n            view.set_version(MAIN_DOCK_AREA.version, window, cx);\n            view.set_center(dock_item, window, cx);\n            view.set_left_dock(left_panels, Some(px(350.)), true, window, cx);\n            view.set_bottom_dock(bottom_panels, Some(px(200.)), true, window, cx);\n            view.set_right_dock(right_panels, Some(px(320.)), true, window, cx);\n\n            Self::save_state(&view.dump(cx)).unwrap();\n        });\n    }\n\n    fn init_default_layout(\n        dock_area: &WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> DockItem {\n        DockItem::v_split(\n            vec![DockItem::tabs(\n                vec![\n                    Arc::new(StoryContainer::panel::<ButtonStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<InputStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<SelectStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<LabelStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<DialogStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<PopoverStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<SwitchStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<ProgressStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<DataTableStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<ImageStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<IconStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<TooltipStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<CalendarStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<ResizableStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<ScrollbarStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<AccordionStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<SidebarStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<FormStory>(window, cx)),\n                    Arc::new(StoryContainer::panel::<NotificationStory>(window, cx)),\n                ],\n                &dock_area,\n                window,\n                cx,\n            )],\n            &dock_area,\n            window,\n            cx,\n        )\n    }\n\n    pub fn new_local(cx: &mut App) -> Task<anyhow::Result<WindowHandle<Root>>> {\n        let mut window_size = size(px(1600.0), px(1200.0));\n        if let Some(display) = cx.primary_display() {\n            let display_size = display.bounds().size;\n            window_size.width = window_size.width.min(display_size.width * 0.85);\n            window_size.height = window_size.height.min(display_size.height * 0.85);\n        }\n\n        let window_bounds = Bounds::centered(None, window_size, cx);\n\n        cx.spawn(async move |cx| {\n            let options = WindowOptions {\n                window_bounds: Some(WindowBounds::Windowed(window_bounds)),\n                #[cfg(not(target_os = \"linux\"))]\n                titlebar: Some(gpui_component::TitleBar::title_bar_options()),\n                window_min_size: Some(gpui::Size {\n                    width: px(640.),\n                    height: px(480.),\n                }),\n                #[cfg(target_os = \"linux\")]\n                window_background: gpui::WindowBackgroundAppearance::Transparent,\n                #[cfg(target_os = \"linux\")]\n                window_decorations: Some(gpui::WindowDecorations::Client),\n                kind: WindowKind::Normal,\n                ..Default::default()\n            };\n\n            let window = cx.open_window(options, |window, cx| {\n                let story_view = cx.new(|cx| StoryWorkspace::new(window, cx));\n                cx.new(|cx| Root::new(story_view, window, cx))\n            })?;\n\n            window\n                .update(cx, |_, window, cx| {\n                    window.activate_window();\n                    window.set_window_title(\"GPUI App\");\n                    cx.on_release(|_, cx| {\n                        // exit app\n                        cx.quit();\n                    })\n                    .detach();\n                })\n                .expect(\"failed to update window\");\n\n            Ok(window)\n        })\n    }\n\n    fn on_action_add_panel(\n        &mut self,\n        action: &AddPanel,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        // Random pick up a panel to add\n        let panel = match rand::random::<usize>() % 18 {\n            0 => Arc::new(StoryContainer::panel::<ButtonStory>(window, cx)),\n            1 => Arc::new(StoryContainer::panel::<InputStory>(window, cx)),\n            2 => Arc::new(StoryContainer::panel::<SelectStory>(window, cx)),\n            3 => Arc::new(StoryContainer::panel::<LabelStory>(window, cx)),\n            4 => Arc::new(StoryContainer::panel::<DialogStory>(window, cx)),\n            5 => Arc::new(StoryContainer::panel::<PopoverStory>(window, cx)),\n            6 => Arc::new(StoryContainer::panel::<SwitchStory>(window, cx)),\n            7 => Arc::new(StoryContainer::panel::<ProgressStory>(window, cx)),\n            8 => Arc::new(StoryContainer::panel::<DataTableStory>(window, cx)),\n            9 => Arc::new(StoryContainer::panel::<ImageStory>(window, cx)),\n            10 => Arc::new(StoryContainer::panel::<IconStory>(window, cx)),\n            11 => Arc::new(StoryContainer::panel::<TooltipStory>(window, cx)),\n            12 => Arc::new(StoryContainer::panel::<ProgressStory>(window, cx)),\n            13 => Arc::new(StoryContainer::panel::<CalendarStory>(window, cx)),\n            14 => Arc::new(StoryContainer::panel::<ResizableStory>(window, cx)),\n            15 => Arc::new(StoryContainer::panel::<ScrollbarStory>(window, cx)),\n            16 => Arc::new(StoryContainer::panel::<AccordionStory>(window, cx)),\n            _ => Arc::new(StoryContainer::panel::<ButtonStory>(window, cx)),\n        };\n\n        self.dock_area.update(cx, |dock_area, cx| {\n            dock_area.add_panel(panel, action.0, None, window, cx);\n        });\n    }\n\n    fn on_action_toggle_panel_visible(\n        &mut self,\n        action: &TogglePanelVisible,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let panel_name = action.0.clone();\n        let invisible_panels = AppState::global(cx).invisible_panels.clone();\n        invisible_panels.update(cx, |names, cx| {\n            if names.contains(&panel_name) {\n                names.retain(|id| id != &panel_name);\n            } else {\n                names.push(panel_name);\n            }\n            cx.notify();\n        });\n        cx.notify();\n    }\n\n    fn on_action_toggle_dock_toggle_button(\n        &mut self,\n        _: &ToggleDockToggleButton,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.toggle_button_visible = !self.toggle_button_visible;\n\n        self.dock_area.update(cx, |dock_area, cx| {\n            dock_area.set_toggle_button_visible(self.toggle_button_visible, cx);\n        });\n    }\n}\n\npub fn open_new(\n    cx: &mut App,\n    init: impl FnOnce(&mut Root, &mut Window, &mut Context<Root>) + 'static + Send,\n) -> Task<()> {\n    let task: Task<std::result::Result<WindowHandle<Root>, anyhow::Error>> =\n        StoryWorkspace::new_local(cx);\n    cx.spawn(async move |cx| {\n        if let Some(root) = task.await.ok() {\n            root.update(cx, |workspace, window, cx| init(workspace, window, cx))\n                .expect(\"failed to init workspace\");\n        }\n    })\n}\n\nimpl Render for StoryWorkspace {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let sheet_layer = Root::render_sheet_layer(window, cx);\n        let dialog_layer = Root::render_dialog_layer(window, cx);\n        let notification_layer = Root::render_notification_layer(window, cx);\n\n        div()\n            .id(\"story-workspace\")\n            .on_action(cx.listener(Self::on_action_add_panel))\n            .on_action(cx.listener(Self::on_action_toggle_panel_visible))\n            .on_action(cx.listener(Self::on_action_toggle_dock_toggle_button))\n            .relative()\n            .size_full()\n            .flex()\n            .flex_col()\n            .child(self.title_bar.clone())\n            .child(self.dock_area.clone())\n            .children(sheet_layer)\n            .children(dialog_layer)\n            .children(notification_layer)\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application().with_assets(Assets);\n\n    app.run(move |cx| {\n        init(cx);\n\n        open_new(cx, |_, _, _| {\n            // do something\n        })\n        .detach();\n    });\n}\n"
  },
  {
    "path": "crates/story/examples/editor.rs",
    "content": "use std::{\n    ops::Range,\n    path::PathBuf,\n    rc::Rc,\n    str::FromStr,\n    sync::{Arc, RwLock},\n    time::Duration,\n};\n\nuse autocorrect::ignorer::Ignorer;\nuse gpui::{prelude::FluentBuilder, *};\nuse gpui_component::{\n    ActiveTheme, IconName, Sizable, WindowExt,\n    button::{Button, ButtonVariants as _},\n    h_flex,\n    highlighter::{Diagnostic, DiagnosticSeverity, Language, LanguageConfig, LanguageRegistry},\n    input::{\n        self, CodeActionProvider, CompletionProvider, DefinitionProvider, DocumentColorProvider,\n        HoverProvider, Input, InputEvent, InputState, Position, Rope, RopeExt, TabSize,\n    },\n    list::ListItem,\n    resizable::{h_resizable, resizable_panel},\n    tree::{TreeItem, TreeState, tree},\n    v_flex,\n};\nuse gpui_component_assets::Assets;\nuse gpui_component_story::Open;\nuse lsp_types::{\n    CodeAction, CodeActionKind, CompletionContext, CompletionItem, CompletionResponse,\n    CompletionTextEdit, InlineCompletionContext, InlineCompletionItem, InlineCompletionResponse,\n    InsertReplaceEdit, InsertTextFormat, TextEdit, WorkspaceEdit,\n};\n\nenum Lang {\n    BuiltIn(Language),\n    External(&'static str),\n}\n\nimpl Lang {\n    fn name(&self) -> &str {\n        match self {\n            Lang::BuiltIn(lang) => lang.name(),\n            Lang::External(lang) => lang,\n        }\n    }\n\n    fn from_str(s: &str) -> Self {\n        match s {\n            \"nv\" => Lang::External(\"navi\"),\n            _ => Lang::BuiltIn(Language::from_str(s)),\n        }\n    }\n}\n\nfn init() {\n    LanguageRegistry::singleton().register(\n        \"navi\",\n        &LanguageConfig::new(\n            \"navi\",\n            tree_sitter_navi::LANGUAGE.into(),\n            vec![],\n            tree_sitter_navi::HIGHLIGHTS_QUERY,\n            \"\",\n            \"\",\n        ),\n    );\n}\n\npub struct Example {\n    editor: Entity<InputState>,\n    tree_state: Entity<TreeState>,\n    go_to_line_state: Entity<InputState>,\n    language: Lang,\n    line_number: bool,\n    indent_guides: bool,\n    soft_wrap: bool,\n    show_whitespaces: bool,\n    folding: bool,\n    lsp_store: ExampleLspStore,\n    _subscriptions: Vec<Subscription>,\n    _lint_task: Task<()>,\n}\n\n#[derive(Clone)]\npub struct ExampleLspStore {\n    completions: Arc<Vec<CompletionItem>>,\n    code_actions: Arc<RwLock<Vec<(Range<usize>, CodeAction)>>>,\n    diagnostics: Arc<RwLock<Vec<Diagnostic>>>,\n    dirty: Arc<RwLock<bool>>,\n}\n\nimpl ExampleLspStore {\n    pub fn new() -> Self {\n        let completions = serde_json::from_slice::<Vec<CompletionItem>>(include_bytes!(\n            \"./fixtures/completion_items.json\"\n        ))\n        .unwrap();\n\n        Self {\n            completions: Arc::new(completions),\n            code_actions: Arc::new(RwLock::new(vec![])),\n            diagnostics: Arc::new(RwLock::new(vec![])),\n            dirty: Arc::new(RwLock::new(false)),\n        }\n    }\n\n    fn diagnostics(&self) -> Vec<Diagnostic> {\n        let guard = self.diagnostics.read().unwrap();\n        guard.clone()\n    }\n\n    fn update_diagnostics(&self, diagnostics: Vec<Diagnostic>) {\n        let mut guard = self.diagnostics.write().unwrap();\n        *guard = diagnostics;\n        *self.dirty.write().unwrap() = true;\n    }\n\n    fn code_actions(&self) -> Vec<(Range<usize>, CodeAction)> {\n        let guard = self.code_actions.read().unwrap();\n        guard.clone()\n    }\n\n    fn update_code_actions(&self, code_actions: Vec<(Range<usize>, CodeAction)>) {\n        let mut guard = self.code_actions.write().unwrap();\n        *guard = code_actions;\n        *self.dirty.write().unwrap() = true;\n    }\n\n    fn is_dirty(&self) -> bool {\n        let guard = self.dirty.read().unwrap();\n        *guard\n    }\n}\n\nfn completion_item(\n    replace_range: &lsp_types::Range,\n    label: &str,\n    replace_text: &str,\n    documentation: &str,\n) -> CompletionItem {\n    CompletionItem {\n        label: label.to_string(),\n        kind: Some(lsp_types::CompletionItemKind::FUNCTION),\n        text_edit: Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {\n            new_text: replace_text.to_string(),\n            insert: *replace_range,\n            replace: *replace_range,\n        })),\n        documentation: Some(lsp_types::Documentation::String(documentation.to_string())),\n        insert_text: None,\n        ..Default::default()\n    }\n}\n\nimpl CompletionProvider for ExampleLspStore {\n    fn completions(\n        &self,\n        rope: &Rope,\n        offset: usize,\n        trigger: CompletionContext,\n        _: &mut Window,\n        cx: &mut Context<InputState>,\n    ) -> Task<Result<CompletionResponse>> {\n        let trigger_character = trigger.trigger_character.unwrap_or_default();\n        if trigger_character.is_empty() {\n            return Task::ready(Ok(CompletionResponse::Array(vec![])));\n        }\n\n        // Simulate to delay for fetching completions\n        let rope = rope.clone();\n        let items = self.completions.clone();\n        cx.background_spawn(async move {\n            // Simulate a slow completion source, to test Editor async handling.\n            smol::Timer::after(Duration::from_millis(20)).await;\n\n            if trigger_character.starts_with(\"/\") {\n                let start = offset.saturating_sub(trigger_character.len());\n                let start_pos = rope.offset_to_position(start);\n                let end_pos = rope.offset_to_position(offset);\n                let replace_range = lsp_types::Range::new(start_pos, end_pos);\n\n                let items = vec![\n                    completion_item(\n                        &replace_range,\n                        \"/date\",\n                        format!(\"{}\", chrono::Local::now().date_naive()).as_str(),\n                        \"Insert current date\",\n                    ),\n                    completion_item(&replace_range, \"/thanks\", \"Thank you!\", \"Insert Thank you!\"),\n                    completion_item(&replace_range, \"/+1\", \"👍\", \"Insert 👍\"),\n                    completion_item(&replace_range, \"/-1\", \"👎\", \"Insert 👎\"),\n                    completion_item(&replace_range, \"/smile\", \"😊\", \"Insert 😊\"),\n                    completion_item(&replace_range, \"/sad\", \"😢\", \"Insert 😢\"),\n                    completion_item(&replace_range, \"/launch\", \"🚀\", \"Insert 🚀\"),\n                ];\n                return Ok(CompletionResponse::Array(items));\n            }\n\n            let items = items\n                .iter()\n                .filter(|item| item.label.starts_with(&trigger_character))\n                .take(10)\n                .map(|item| {\n                    let mut item = item.clone();\n                    item.insert_text = Some(item.label.replace(&trigger_character, \"\"));\n                    item\n                })\n                .collect::<Vec<_>>();\n\n            Ok(CompletionResponse::Array(items))\n        })\n    }\n\n    fn inline_completion(\n        &self,\n        rope: &Rope,\n        offset: usize,\n        _trigger: InlineCompletionContext,\n        _window: &mut Window,\n        cx: &mut Context<InputState>,\n    ) -> Task<Result<InlineCompletionResponse>> {\n        let rope = rope.clone();\n        cx.background_spawn(async move {\n            // Get the current line text before cursor using RopeExt\n            let point = rope.offset_to_point(offset);\n            let line_start = rope.line_start_offset(point.row);\n            let current_line = rope.slice(line_start..offset).to_string();\n\n            // Simple pattern matching for demo\n            let suggestion =\n                if current_line.trim_start().starts_with(\"fn \") && !current_line.contains('{') {\n                    Some(\"() {\\n    // Write your code here..\\n}\".into())\n                } else {\n                    None\n                };\n\n            if let Some(insert_text) = suggestion {\n                Ok(InlineCompletionResponse::Array(vec![\n                    InlineCompletionItem {\n                        insert_text,\n                        filter_text: None,\n                        range: None,\n                        command: None,\n                        insert_text_format: Some(InsertTextFormat::SNIPPET),\n                    },\n                ]))\n            } else {\n                Ok(InlineCompletionResponse::Array(vec![]))\n            }\n        })\n    }\n\n    fn is_completion_trigger(\n        &self,\n        _offset: usize,\n        _new_text: &str,\n        _cx: &mut Context<InputState>,\n    ) -> bool {\n        true\n    }\n}\n\nimpl CodeActionProvider for ExampleLspStore {\n    fn id(&self) -> SharedString {\n        \"LspStore\".into()\n    }\n\n    fn code_actions(\n        &self,\n        _state: Entity<InputState>,\n        range: Range<usize>,\n        _window: &mut Window,\n        _cx: &mut App,\n    ) -> Task<Result<Vec<CodeAction>>> {\n        let mut actions = vec![];\n        for (node_range, code_action) in self.code_actions().iter() {\n            if !(range.start >= node_range.start && range.end <= node_range.end) {\n                continue;\n            }\n\n            actions.push(code_action.clone());\n        }\n\n        Task::ready(Ok(actions))\n    }\n\n    fn perform_code_action(\n        &self,\n        state: Entity<InputState>,\n        action: CodeAction,\n        _push_to_history: bool,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Task<Result<()>> {\n        let Some(edit) = action.edit else {\n            return Task::ready(Ok(()));\n        };\n\n        let Some((_, text_edits)) = if let Some(changes) = edit.changes {\n            changes\n        } else {\n            return Task::ready(Ok(()));\n        }\n        .into_iter()\n        .next() else {\n            return Task::ready(Ok(()));\n        };\n\n        let state = state.downgrade();\n        window.spawn(cx, async move |cx| {\n            state.update_in(cx, |state, window, cx| {\n                state.apply_lsp_edits(&text_edits, window, cx);\n            })\n        })\n    }\n}\n\nimpl HoverProvider for ExampleLspStore {\n    fn hover(\n        &self,\n        text: &Rope,\n        offset: usize,\n        _window: &mut Window,\n        _cx: &mut App,\n    ) -> Task<Result<Option<lsp_types::Hover>>> {\n        let word = text.word_at(offset);\n        if word.is_empty() {\n            return Task::ready(Ok(None));\n        }\n\n        let Some(item) = self.completions.iter().find(|item| item.label == word) else {\n            return Task::ready(Ok(None));\n        };\n\n        let contents = if let Some(doc) = &item.documentation {\n            match doc {\n                lsp_types::Documentation::String(s) => s.clone(),\n                lsp_types::Documentation::MarkupContent(mc) => mc.value.clone(),\n            }\n        } else {\n            \"No documentation available.\".to_string()\n        };\n\n        let hover = lsp_types::Hover {\n            contents: lsp_types::HoverContents::Scalar(lsp_types::MarkedString::String(contents)),\n            range: None,\n        };\n\n        Task::ready(Ok(Some(hover)))\n    }\n}\n\nconst RUST_DOC_URLS: &[(&str, &str)] = &[\n    (\"String\", \"string/struct.String\"),\n    (\"Debug\", \"fmt/trait.Debug\"),\n    (\"Clone\", \"clone/trait.Clone\"),\n    (\"Option\", \"option/enum.Option\"),\n    (\"Result\", \"result/enum.Result\"),\n    (\"Vec\", \"vec/struct.Vec\"),\n    (\"HashMap\", \"collections/hash_map/struct.HashMap\"),\n    (\"HashSet\", \"collections/hash_set/struct.HashSet\"),\n    (\"Arc\", \"sync/struct.Arc\"),\n    (\"RwLock\", \"sync/struct.RwLock\"),\n    (\"Duration\", \"time/struct.Duration\"),\n];\n\nimpl DefinitionProvider for ExampleLspStore {\n    fn definitions(\n        &self,\n        text: &Rope,\n        offset: usize,\n        _window: &mut Window,\n        _cx: &mut App,\n    ) -> Task<Result<Vec<lsp_types::LocationLink>>> {\n        let Some(word_range) = text.word_range(offset) else {\n            return Task::ready(Ok(vec![]));\n        };\n        let word = text.slice(word_range.clone()).to_string();\n\n        let document_uri = lsp_types::Uri::from_str(\"file://example\").unwrap();\n        let start = text.offset_to_position(word_range.start);\n        let end = text.offset_to_position(word_range.end);\n        let symbol_range = lsp_types::Range { start, end };\n\n        if word == \"Duration\" {\n            let target_range = lsp_types::Range {\n                start: lsp_types::Position {\n                    line: 2,\n                    character: 4,\n                },\n                end: lsp_types::Position {\n                    line: 2,\n                    character: 23,\n                },\n            };\n            return Task::ready(Ok(vec![lsp_types::LocationLink {\n                target_uri: document_uri,\n                target_range: target_range,\n                target_selection_range: target_range,\n                origin_selection_range: Some(symbol_range),\n            }]));\n        }\n\n        let names = RUST_DOC_URLS\n            .iter()\n            .map(|(name, _)| *name)\n            .collect::<Vec<_>>();\n        for (ix, t) in names.iter().enumerate() {\n            if *t == word {\n                let url = RUST_DOC_URLS[ix].1;\n                let location = lsp_types::LocationLink {\n                    target_uri: lsp_types::Uri::from_str(&format!(\n                        \"https://doc.rust-lang.org/std/{}.html\",\n                        url\n                    ))\n                    .unwrap(),\n                    target_selection_range: lsp_types::Range::default(),\n                    target_range: lsp_types::Range::default(),\n                    origin_selection_range: Some(symbol_range),\n                };\n\n                return Task::ready(Ok(vec![location]));\n            }\n        }\n\n        Task::ready(Ok(vec![]))\n    }\n}\n\nstruct TextConvertor;\n\nimpl CodeActionProvider for TextConvertor {\n    fn id(&self) -> SharedString {\n        \"TextConvertor\".into()\n    }\n\n    fn code_actions(\n        &self,\n        state: Entity<InputState>,\n        range: Range<usize>,\n        _window: &mut Window,\n        cx: &mut App,\n    ) -> Task<Result<Vec<CodeAction>>> {\n        let mut actions = vec![];\n        if range.is_empty() {\n            return Task::ready(Ok(actions));\n        }\n\n        let state = state.read(cx);\n        let document_uri = lsp_types::Uri::from_str(\"file://example\").unwrap();\n\n        let old_text = state.text().slice(range.clone()).to_string();\n        let start = state.text().offset_to_position(range.start);\n        let end = state.text().offset_to_position(range.end);\n        let range = lsp_types::Range { start, end };\n\n        actions.push(CodeAction {\n            title: \"Convert to Uppercase\".into(),\n            kind: Some(CodeActionKind::REFACTOR),\n            edit: Some(WorkspaceEdit {\n                changes: Some(\n                    std::iter::once((\n                        document_uri.clone(),\n                        vec![TextEdit {\n                            range,\n                            new_text: old_text.to_uppercase(),\n                        }],\n                    ))\n                    .collect(),\n                ),\n                ..Default::default()\n            }),\n            ..Default::default()\n        });\n\n        actions.push(CodeAction {\n            title: \"Convert to Lowercase\".into(),\n            kind: Some(CodeActionKind::REFACTOR),\n            edit: Some(WorkspaceEdit {\n                changes: Some(\n                    std::iter::once((\n                        document_uri.clone(),\n                        vec![TextEdit {\n                            range,\n                            new_text: old_text.to_lowercase(),\n                        }],\n                    ))\n                    .collect(),\n                ),\n                ..Default::default()\n            }),\n            ..Default::default()\n        });\n\n        actions.push(CodeAction {\n            title: \"Titleize\".into(),\n            kind: Some(CodeActionKind::REFACTOR),\n            edit: Some(WorkspaceEdit {\n                changes: Some(\n                    std::iter::once((\n                        document_uri.clone(),\n                        vec![TextEdit {\n                            range,\n                            new_text: old_text\n                                .split_whitespace()\n                                .map(|word| {\n                                    let mut chars = word.chars();\n                                    chars\n                                        .next()\n                                        .map(|c| c.to_uppercase().collect::<String>())\n                                        .unwrap_or_default()\n                                        + chars.as_str()\n                                })\n                                .collect::<Vec<_>>()\n                                .join(\" \"),\n                        }],\n                    ))\n                    .collect(),\n                ),\n                ..Default::default()\n            }),\n            ..Default::default()\n        });\n\n        actions.push(CodeAction {\n            title: \"Capitalize\".into(),\n            kind: Some(CodeActionKind::REFACTOR),\n            edit: Some(WorkspaceEdit {\n                changes: Some(\n                    std::iter::once((\n                        document_uri.clone(),\n                        vec![TextEdit {\n                            range,\n                            new_text: old_text\n                                .chars()\n                                .enumerate()\n                                .map(|(i, c)| {\n                                    if i == 0 {\n                                        c.to_uppercase().to_string()\n                                    } else {\n                                        c.to_string()\n                                    }\n                                })\n                                .collect(),\n                        }],\n                    ))\n                    .collect(),\n                ),\n                ..Default::default()\n            }),\n            ..Default::default()\n        });\n\n        // snake_case\n        actions.push(CodeAction {\n            title: \"Convert to snake_case\".into(),\n            kind: Some(CodeActionKind::REFACTOR),\n            edit: Some(WorkspaceEdit {\n                changes: Some(\n                    std::iter::once((\n                        document_uri.clone(),\n                        vec![TextEdit {\n                            range,\n                            new_text: old_text\n                                .chars()\n                                .enumerate()\n                                .map(|(i, c)| {\n                                    if c.is_uppercase() {\n                                        if i != 0 {\n                                            format!(\"_{}\", c.to_lowercase())\n                                        } else {\n                                            c.to_lowercase().to_string()\n                                        }\n                                    } else {\n                                        c.to_string()\n                                    }\n                                })\n                                .collect(),\n                        }],\n                    ))\n                    .collect(),\n                ),\n                ..Default::default()\n            }),\n            ..Default::default()\n        });\n\n        Task::ready(Ok(actions))\n    }\n\n    fn perform_code_action(\n        &self,\n        state: Entity<InputState>,\n        action: CodeAction,\n        _push_to_history: bool,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Task<Result<()>> {\n        let Some(edit) = action.edit else {\n            return Task::ready(Ok(()));\n        };\n\n        let Some((_, text_edits)) = if let Some(changes) = edit.changes {\n            changes\n        } else {\n            return Task::ready(Ok(()));\n        }\n        .into_iter()\n        .next() else {\n            return Task::ready(Ok(()));\n        };\n\n        let state = state.downgrade();\n        window.spawn(cx, async move |cx| {\n            state.update_in(cx, |state, window, cx| {\n                state.apply_lsp_edits(&text_edits, window, cx);\n            })\n        })\n    }\n}\n\nimpl DocumentColorProvider for ExampleLspStore {\n    fn document_colors(\n        &self,\n        text: &Rope,\n        _window: &mut Window,\n        _cx: &mut App,\n    ) -> Task<gpui::Result<Vec<lsp_types::ColorInformation>>> {\n        let nodes = color_lsp::parse(&text.to_string());\n        let colors = nodes\n            .into_iter()\n            .map(|node| {\n                let start = lsp_types::Position::new(node.position.line, node.position.character);\n                let end = lsp_types::Position::new(\n                    node.position.line,\n                    node.position.character + node.matched.chars().count() as u32,\n                );\n\n                lsp_types::ColorInformation {\n                    range: lsp_types::Range { start, end },\n                    color: lsp_types::Color {\n                        red: node.color.r,\n                        green: node.color.g,\n                        blue: node.color.b,\n                        alpha: node.color.a,\n                    },\n                }\n            })\n            .collect::<Vec<_>>();\n\n        Task::ready(Ok(colors))\n    }\n}\n\nfn build_file_items(ignorer: &Ignorer, root: &PathBuf, path: &PathBuf) -> Vec<TreeItem> {\n    let mut items = Vec::new();\n\n    if let Ok(entries) = std::fs::read_dir(path) {\n        for entry in entries.flatten() {\n            let path = entry.path();\n            let relative_path = path.strip_prefix(root).unwrap_or(&path);\n            if ignorer.is_ignored(&relative_path.to_string_lossy())\n                || relative_path.ends_with(\".git\")\n            {\n                continue;\n            }\n            let file_name = path\n                .file_name()\n                .and_then(|n| n.to_str())\n                .unwrap_or(\"Unknown\")\n                .to_string();\n            let id = path.to_string_lossy().to_string();\n            if path.is_dir() {\n                let children = build_file_items(ignorer, &root, &path);\n                items.push(TreeItem::new(id, file_name).children(children));\n            } else {\n                items.push(TreeItem::new(id, file_name));\n            }\n        }\n    }\n    items.sort_by(|a, b| {\n        b.is_folder()\n            .cmp(&a.is_folder())\n            .then(a.label.cmp(&b.label))\n    });\n    items\n}\n\nimpl Example {\n    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let default_language = Lang::BuiltIn(Language::Rust);\n        let lsp_store = ExampleLspStore::new();\n\n        let editor = cx.new(|cx| {\n            let mut editor = InputState::new(window, cx)\n                .code_editor(default_language.name().to_string())\n                .line_number(true)\n                .indent_guides(true)\n                .tab_size(TabSize {\n                    tab_size: 4,\n                    hard_tabs: false,\n                })\n                .soft_wrap(false)\n                .default_value(include_str!(\"./fixtures/test.rs\"))\n                .placeholder(\"Enter your code here...\");\n\n            let lsp_store = Rc::new(lsp_store.clone());\n            editor.lsp.completion_provider = Some(lsp_store.clone());\n            editor.lsp.code_action_providers = vec![lsp_store.clone(), Rc::new(TextConvertor)];\n            editor.lsp.hover_provider = Some(lsp_store.clone());\n            editor.lsp.definition_provider = Some(lsp_store.clone());\n            editor.lsp.document_color_provider = Some(lsp_store.clone());\n\n            editor\n        });\n        let go_to_line_state = cx.new(|cx| InputState::new(window, cx));\n\n        let tree_state = cx.new(|cx| TreeState::new(cx));\n        Self::load_files(tree_state.clone(), PathBuf::from(\"./\"), cx);\n\n        let _subscriptions = vec![cx.subscribe(&editor, |this, _, _: &InputEvent, cx| {\n            this.lint_document(cx);\n        })];\n\n        Self {\n            editor,\n            tree_state,\n            go_to_line_state,\n            language: default_language,\n            line_number: true,\n            indent_guides: true,\n            soft_wrap: false,\n            show_whitespaces: false,\n            folding: true,\n            lsp_store,\n            _subscriptions,\n            _lint_task: Task::ready(()),\n        }\n    }\n\n    fn load_files(state: Entity<TreeState>, path: PathBuf, cx: &mut App) {\n        cx.spawn(async move |cx| {\n            let ignorer = Ignorer::new(&path.to_string_lossy());\n            let items = build_file_items(&ignorer, &path, &path);\n            _ = state.update(cx, |state, cx| {\n                state.set_items(items, cx);\n            });\n        })\n        .detach();\n    }\n\n    fn go_to_line(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {\n        let editor = self.editor.clone();\n        let input_state = self.go_to_line_state.clone();\n\n        window.open_alert_dialog(cx, move |dialog, window, cx| {\n            input_state.update(cx, |state, cx| {\n                let cursor_pos = editor.read(cx).cursor_position();\n                state.set_placeholder(\n                    format!(\"{}:{}\", cursor_pos.line, cursor_pos.character),\n                    window,\n                    cx,\n                );\n                state.focus(window, cx);\n            });\n\n            dialog\n                .title(\"Go to line\")\n                .child(Input::new(&input_state))\n                .on_ok({\n                    let editor = editor.clone();\n                    let input_state = input_state.clone();\n                    move |_, window, cx| {\n                        let query = input_state.read(cx).value();\n                        let mut parts = query\n                            .split(':')\n                            .map(|s| s.trim().parse::<usize>().ok())\n                            .collect::<Vec<_>>()\n                            .into_iter();\n                        let Some(line) = parts.next().and_then(|l| l) else {\n                            return false;\n                        };\n                        let column = parts.next().and_then(|c| c).unwrap_or(1);\n                        let position = input::Position::new(\n                            line.saturating_sub(1) as u32,\n                            column.saturating_sub(1) as u32,\n                        );\n\n                        editor.update(cx, |state, cx| {\n                            state.set_cursor_position(position, window, cx);\n                        });\n\n                        true\n                    }\n                })\n        });\n    }\n\n    fn lint_document(&mut self, cx: &mut Context<Self>) {\n        let language = self.language.name().to_string();\n        let lsp_store = self.lsp_store.clone();\n        let text = self.editor.read(cx).text().clone();\n\n        self._lint_task = cx.background_spawn(async move {\n            let value = text.to_string();\n            let result = autocorrect::lint_for(value.as_str(), &language);\n\n            let mut code_actions = vec![];\n            let mut diagnostics = vec![];\n\n            for item in result.lines.iter() {\n                let severity = match item.severity {\n                    autocorrect::Severity::Error => DiagnosticSeverity::Warning,\n                    autocorrect::Severity::Warning => DiagnosticSeverity::Hint,\n                    autocorrect::Severity::Pass => DiagnosticSeverity::Info,\n                };\n\n                let line = item.line.saturating_sub(1); // Convert to 0-based index\n                let col = item.col.saturating_sub(1); // Convert to 0-based index\n\n                let start = Position::new(line as u32, col as u32);\n                let end = Position::new(line as u32, (col + item.old.chars().count()) as u32);\n                let message = format!(\"AutoCorrect: {}\", item.new);\n                diagnostics.push(Diagnostic::new(start..end, message).with_severity(severity));\n\n                let range = text.position_to_offset(&start)..text.position_to_offset(&end);\n\n                let text_edit = TextEdit {\n                    range: lsp_types::Range { start, end },\n                    new_text: item.new.clone(),\n                };\n\n                let edit = WorkspaceEdit {\n                    changes: Some(\n                        std::iter::once((\n                            lsp_types::Uri::from_str(\"file://example\").unwrap(),\n                            vec![text_edit],\n                        ))\n                        .collect(),\n                    ),\n                    ..Default::default()\n                };\n\n                code_actions.push((\n                    range,\n                    CodeAction {\n                        title: format!(\"Change to '{}'\", item.new),\n                        kind: Some(CodeActionKind::QUICKFIX),\n                        edit: Some(edit),\n                        ..Default::default()\n                    },\n                ));\n            }\n\n            lsp_store.update_code_actions(code_actions.clone());\n            lsp_store.update_diagnostics(diagnostics.clone());\n        });\n    }\n\n    fn on_action_open(&mut self, _: &Open, window: &mut Window, cx: &mut Context<Self>) {\n        let path = cx.prompt_for_paths(PathPromptOptions {\n            files: true,\n            directories: true,\n            multiple: false,\n            prompt: Some(\"Select a source file\".into()),\n        });\n\n        let view = cx.entity();\n        cx.spawn_in(window, async move |_, window| {\n            let path = path.await.ok()?.ok()??.iter().next()?.clone();\n\n            window\n                .update(|window, cx| Self::open_file(view, path, window, cx))\n                .ok()\n        })\n        .detach();\n    }\n\n    fn open_file(\n        view: Entity<Self>,\n        path: PathBuf,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Result<()> {\n        let language = path\n            .extension()\n            .and_then(|ext| ext.to_str())\n            .unwrap_or_default();\n        let language = Lang::from_str(&language);\n        let content = std::fs::read_to_string(&path)?;\n\n        window\n            .spawn(cx, async move |window| {\n                _ = view.update_in(window, |this, window, cx| {\n                    _ = this.editor.update(cx, |this, cx| {\n                        this.set_highlighter(language.name().to_string(), cx);\n                        this.set_value(content, window, cx);\n                    });\n\n                    this.language = language;\n                    cx.notify();\n                });\n            })\n            .detach();\n\n        Ok(())\n    }\n\n    fn render_file_tree(&self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let view = cx.entity();\n        tree(\n            &self.tree_state,\n            move |ix, entry, _selected, _window, cx| {\n                view.update(cx, |_, cx| {\n                    let item = entry.item();\n                    let icon = if !entry.is_folder() {\n                        IconName::File\n                    } else if entry.is_expanded() {\n                        IconName::FolderOpen\n                    } else {\n                        IconName::Folder\n                    };\n\n                    ListItem::new(ix)\n                        .w_full()\n                        .rounded(cx.theme().radius)\n                        .py_0p5()\n                        .px_2()\n                        .pl(px(16.) * entry.depth() + px(8.))\n                        .child(h_flex().gap_2().child(icon).child(item.label.clone()))\n                        .on_click(cx.listener({\n                            let item = item.clone();\n                            move |_, _, _window, cx| {\n                                if item.is_folder() {\n                                    return;\n                                }\n\n                                Self::open_file(\n                                    cx.entity(),\n                                    PathBuf::from(item.id.as_str()),\n                                    _window,\n                                    cx,\n                                )\n                                .ok();\n\n                                cx.notify();\n                            }\n                        }))\n                })\n            },\n        )\n        .text_sm()\n        .p_1()\n        .bg(cx.theme().sidebar)\n        .text_color(cx.theme().sidebar_foreground)\n        .h_full()\n    }\n\n    fn render_line_number_button(\n        &self,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> impl IntoElement {\n        Button::new(\"line-number\")\n            .when(self.line_number, |this| this.icon(IconName::Check))\n            .label(\"Line Number\")\n            .ghost()\n            .xsmall()\n            .on_click(cx.listener(|this, _, window, cx| {\n                this.line_number = !this.line_number;\n                this.editor.update(cx, |state, cx| {\n                    state.set_line_number(this.line_number, window, cx);\n                });\n                cx.notify();\n            }))\n    }\n\n    fn render_soft_wrap_button(&self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        Button::new(\"soft-wrap\")\n            .ghost()\n            .xsmall()\n            .when(self.soft_wrap, |this| this.icon(IconName::Check))\n            .label(\"Soft Wrap\")\n            .on_click(cx.listener(|this, _, window, cx| {\n                this.soft_wrap = !this.soft_wrap;\n                this.editor.update(cx, |state, cx| {\n                    state.set_soft_wrap(this.soft_wrap, window, cx);\n                });\n                cx.notify();\n            }))\n    }\n\n    fn render_show_whitespaces_button(\n        &self,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> impl IntoElement {\n        Button::new(\"show-whitespace\")\n            .ghost()\n            .xsmall()\n            .when(self.show_whitespaces, |this| this.icon(IconName::Check))\n            .label(\"Show Whitespaces\")\n            .on_click(cx.listener(|this, _, window, cx| {\n                this.show_whitespaces = !this.show_whitespaces;\n                this.editor.update(cx, |state, cx| {\n                    state.set_show_whitespaces(this.show_whitespaces, window, cx);\n                });\n                cx.notify();\n            }))\n    }\n\n    fn render_indent_guides_button(\n        &self,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> impl IntoElement {\n        Button::new(\"indent-guides\")\n            .ghost()\n            .xsmall()\n            .when(self.indent_guides, |this| this.icon(IconName::Check))\n            .label(\"Indent Guides\")\n            .on_click(cx.listener(|this, _, window, cx| {\n                this.indent_guides = !this.indent_guides;\n                this.editor.update(cx, |state, cx| {\n                    state.set_indent_guides(this.indent_guides, window, cx);\n                });\n                cx.notify();\n            }))\n    }\n\n    fn render_folding_button(&self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        Button::new(\"folding\")\n            .ghost()\n            .xsmall()\n            .when(self.folding, |this| this.icon(IconName::Check))\n            .label(\"Folding\")\n            .on_click(cx.listener(|this, _, window, cx| {\n                this.folding = !this.folding;\n                this.editor.update(cx, |state, cx| {\n                    state.set_folding(this.folding, window, cx);\n                });\n                cx.notify();\n            }))\n    }\n\n    fn render_go_to_line_button(&self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let position = self.editor.read(cx).cursor_position();\n        let cursor = self.editor.read(cx).cursor();\n\n        Button::new(\"line-column\")\n            .ghost()\n            .xsmall()\n            .label(format!(\n                \"{}:{} ({} byte)\",\n                position.line + 1,\n                position.character + 1,\n                cursor\n            ))\n            .on_click(cx.listener(Self::go_to_line))\n    }\n}\n\nimpl Render for Example {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        // Update diagnostics\n        if self.lsp_store.is_dirty() {\n            let diagnostics = self.lsp_store.diagnostics();\n            self.editor.update(cx, |state, cx| {\n                _ = state.diagnostics_mut().map(|set| {\n                    set.clear();\n                    set.extend(diagnostics);\n                });\n                cx.notify();\n            });\n        }\n\n        v_flex()\n            .id(\"app\")\n            .size_full()\n            .on_action(cx.listener(Self::on_action_open))\n            .child(\n                v_flex()\n                    .id(\"source\")\n                    .w_full()\n                    .flex_1()\n                    .child(\n                        h_resizable(\"editor-container\")\n                            .child(\n                                resizable_panel()\n                                    .size(px(240.))\n                                    .child(self.render_file_tree(window, cx)),\n                            )\n                            .child(\n                                Input::new(&self.editor)\n                                    .bordered(false)\n                                    .p_0()\n                                    .h_full()\n                                    .font_family(cx.theme().mono_font_family.clone())\n                                    .text_size(cx.theme().mono_font_size)\n                                    .focus_bordered(false)\n                                    .into_any_element(),\n                            ),\n                    )\n                    .child(\n                        h_flex()\n                            .justify_between()\n                            .text_sm()\n                            .bg(cx.theme().background)\n                            .py_1p5()\n                            .px_4()\n                            .border_t_1()\n                            .border_color(cx.theme().border)\n                            .text_color(cx.theme().muted_foreground)\n                            .child(\n                                h_flex()\n                                    .gap_3()\n                                    .child(self.render_line_number_button(window, cx))\n                                    .child(self.render_soft_wrap_button(window, cx))\n                                    .child(self.render_show_whitespaces_button(window, cx))\n                                    .child(self.render_indent_guides_button(window, cx))\n                                    .child(self.render_folding_button(window, cx)),\n                            )\n                            .child(self.render_go_to_line_button(window, cx)),\n                    ),\n            )\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application().with_assets(Assets);\n\n    app.run(move |cx| {\n        gpui_component_story::init(cx);\n        init();\n        cx.activate(true);\n\n        gpui_component_story::create_new_window_with_size(\n            \"Editor\",\n            Some(size(px(1200.), px(750.))),\n            |window, cx| cx.new(|cx| Example::new(window, cx)),\n            cx,\n        );\n    });\n}\n"
  },
  {
    "path": "crates/story/examples/fixtures/completion_items.json",
    "content": "[\n  {\n    \"label\": \"as\",\n    \"detail\": \"as TYPE\",\n    \"documentation\": \"Perform a safe type cast.\\n\\n## Example:\\n```rust\\nlet x: u32 = 42;\\nlet y: u64 = x as u64;\\n```\"\n  },\n  {\n    \"label\": \"break\",\n    \"documentation\": \"Exit a loop immediately.\\n\\n**Usage:**\\nThe `break` statement is used to terminate a loop early when a specific condition is met.\"\n  },\n  {\n    \"label\": \"const\",\n    \"detail\": \"const NAME: Type = ..;\",\n    \"documentation\": \"Define a constant value.\\n\\n**Note:** Constants are immutable and must have their type explicitly declared.\"\n  },\n  {\n    \"label\": \"continue\",\n    \"documentation\": \"Skip the rest of the current loop iteration.\"\n  },\n  {\n    \"label\": \"crate\",\n    \"documentation\": \"Refer to the current crate root.\\n\\n**Details:**\\nThe `crate` keyword is used to access items in the root of the current crate.\"\n  },\n  {\n    \"label\": \"else\",\n    \"documentation\": \"Provide an alternative branch for an if statement.\"\n  },\n  {\n    \"label\": \"enum\",\n    \"documentation\": \"Define a type that can be one of several variants.\\n\\n**Example:**\\n```rust\\nenum Direction {\\n    Up,\\n    Down,\\n    Left,\\n    Right,\\n}\\n```\"\n  },\n  {\n    \"label\": \"extern\",\n    \"documentation\": \"Link to or define external functions or variables.\"\n  },\n  {\n    \"label\": \"false\",\n    \"documentation\": \"Boolean false value.\"\n  },\n  {\n    \"label\": \"fn\",\n    \"documentation\": \"Define a function.\\n\\n**Example:**\\n```rust\\nfn add(a: i32, b: i32) -> i32 {\\n    a + b\\n}\\n```\"\n  },\n  {\n    \"label\": \"for\",\n    \"documentation\": \"Loop over an iterator.\"\n  },\n  {\n    \"label\": \"if\",\n    \"documentation\": \"Conditionally execute code.\"\n  },\n  {\n    \"label\": \"impl\",\n    \"documentation\": \"Implement methods or traits for a type.\"\n  },\n  {\n    \"label\": \"in\",\n    \"documentation\": \"Used in for loops to specify the iterator.\"\n  },\n  {\n    \"label\": \"let\",\n    \"documentation\": \"Bind a value to a variable.\"\n  },\n  {\n    \"label\": \"loop\",\n    \"documentation\": \"Create an infinite loop.\\n\\n**Example:**\\n```rust\\nloop {\\n    println!(\\\"This will run forever!\\\");\\n}\\n```\"\n  },\n  {\n    \"label\": \"match\",\n    \"documentation\": \"Pattern matching control flow construct.\"\n  },\n  {\n    \"label\": \"mod\",\n    \"documentation\": \"Define a module.\"\n  },\n  {\n    \"label\": \"move\",\n    \"documentation\": \"Force a closure to take ownership of captured variables.\"\n  },\n  {\n    \"label\": \"mut\",\n    \"documentation\": \"Make a variable mutable.\"\n  },\n  {\n    \"label\": \"pub\",\n    \"documentation\": \"Make an item public.\"\n  },\n  {\n    \"label\": \"ref\",\n    \"documentation\": \"Create a reference in a pattern.\"\n  },\n  {\n    \"label\": \"return\",\n    \"documentation\": \"Return a value from a function.\"\n  },\n  {\n    \"label\": \"self\",\n    \"documentation\": \"Refer to the current instance of a type.\"\n  },\n  {\n    \"label\": \"Self\",\n    \"documentation\": \"Refer to the current type.\"\n  },\n  {\n    \"label\": \"static\",\n    \"documentation\": \"Define a static variable with a fixed address.\"\n  },\n  {\n    \"label\": \"struct\",\n    \"documentation\": \"Define a structure.\\n\\n**Example:**\\n```rust\\nstruct Point {\\n    x: i32,\\n    y: i32,\\n}\\n```\"\n  },\n  {\n    \"label\": \"super\",\n    \"documentation\": \"Refer to the parent module.\"\n  },\n  {\n    \"label\": \"trait\",\n    \"documentation\": \"Define shared behavior via methods.\"\n  },\n  {\n    \"label\": \"true\",\n    \"documentation\": \"Boolean true value.\"\n  },\n  {\n    \"label\": \"type\",\n    \"documentation\": \"Define a type alias.\"\n  },\n  {\n    \"label\": \"unsafe\",\n    \"documentation\": \"Access potentially unsafe operations.\"\n  },\n  {\n    \"label\": \"use\",\n    \"documentation\": \"Import items from a module.\"\n  },\n  {\n    \"label\": \"where\",\n    \"documentation\": \"Add constraints to generic parameters.\"\n  },\n  {\n    \"label\": \"while\",\n    \"documentation\": \"Loop while a condition is true.\"\n  },\n  {\n    \"label\": \"async\",\n    \"documentation\": \"Define asynchronous code.\"\n  },\n  {\n    \"label\": \"await\",\n    \"documentation\": \"Wait for an asynchronous operation to complete.\"\n  },\n  {\n    \"label\": \"dyn\",\n    \"documentation\": \"Define a dynamically dispatched trait object.\"\n  },\n  {\n    \"label\": \"union\",\n    \"documentation\": \"Define a union type.\"\n  },\n  {\n    \"label\": \"default\",\n    \"documentation\": \"Provide a default implementation for a trait.\"\n  },\n  {\n    \"label\": \"macro_rules\",\n    \"documentation\": \"Define a macro.\"\n  },\n  {\n    \"label\": \"alloc\",\n    \"documentation\": \"Memory allocation module.\"\n  },\n  {\n    \"label\": \"core\",\n    \"documentation\": \"Minimal runtime library for Rust.\"\n  },\n  {\n    \"label\": \"std\",\n    \"documentation\": \"Standard library for Rust.\"\n  },\n  {\n    \"label\": \"vec!\",\n    \"documentation\": \"Create a growable array.\\n\\n**Example:**\\n```rust\\nlet mut v = Vec::new();\\nv.push(1);\\nv.push(2);\\n```\"\n  },\n  {\n    \"label\": \"format!\",\n    \"documentation\": \"Format a string.\"\n  },\n  {\n    \"label\": \"println!\",\n    \"documentation\": \"Print to the standard output.\\n\\n**Example:**\\n```rust\\nprintln!(\\\"Hello, world!\\\");\\n```\"\n  },\n  {\n    \"label\": \"eprintln!\",\n    \"documentation\": \"Print to the standard error.\"\n  },\n  {\n    \"label\": \"dbg!\",\n    \"documentation\": \"Debug print a value.\"\n  },\n  {\n    \"label\": \"todo!\",\n    \"documentation\": \"Mark unfinished code.\"\n  },\n  {\n    \"label\": \"unimplemented!\",\n    \"documentation\": \"Mark unimplemented code.\"\n  },\n  {\n    \"label\": \"unreachable!\",\n    \"documentation\": \"Mark code that should never be executed.\"\n  },\n  {\n    \"label\": \"unimplemented!(\\\"your format message: {}\\\", ...)\",\n    \"documentation\": \"Mark unimplemented code with a custom message.\"\n  }\n]\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.astro",
    "content": "---\n// @ts-nocheck\n// This file demonstrates Astro 5 syntax highlighting features.\n// Frontmatter: TypeScript/JavaScript code that runs at build time\n\nimport type { GetStaticPaths } from 'astro';\n\ninterface Props {\n\ttitle: string;\n\tcount?: number;\n}\n\nconst { title, count = 0 } = Astro.props;\nconst currentYear = new Date().getFullYear();\n\nconst items = ['Astro', 'TypeScript', 'React', 'Vue'];\nconst isProduction = import.meta.env.PROD;\n\nfunction formatGreeting(name: string): string {\n\treturn `Hello, ${name}!`;\n}\n\nconst apiUrl = import.meta.env.PUBLIC_API_URL ?? 'https://api.example.com';\n---\n\n<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width\" />\n\t\t<title>{title} - Astro 5 Demo</title>\n\t\t<style>\n\t\t\t.container {\n\t\t\t\tmax-width: 800px;\n\t\t\t\tmargin: 0 auto;\n\t\t\t\tpadding: 2rem;\n\t\t\t\tfont-family: system-ui, sans-serif;\n\t\t\t}\n\n\t\t\t.highlight {\n\t\t\t\tcolor: #ff5d01;\n\t\t\t\tfont-weight: bold;\n\t\t\t}\n\n\t\t\t.card {\n\t\t\t\tbackground: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n\t\t\t\tpadding: 1.5rem;\n\t\t\t\tborder-radius: 8px;\n\t\t\t\tcolor: white;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\t.container {\n\t\t\t\t\tbackground: #1a1a1a;\n\t\t\t\t\tcolor: #fff;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"container\">\n\t\t\t<h1 class=\"highlight\">{formatGreeting(title)}</h1>\n\n\t\t\t<p>Current year: <strong>{currentYear}</strong></p>\n\t\t\t<p>\n\t\t\t\tEnvironment: <span class=\"highlight\"\n\t\t\t\t\t>{isProduction ? 'Production' : 'Development'}</span\n\t\t\t\t>\n\t\t\t</p>\n\n\t\t\t{\n\t\t\t\tcount > 0 && (\n\t\t\t\t\t<div class=\"card\">\n\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\tYou have {count} {count === 1 ? 'item' : 'items'}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t<ul>\n\t\t\t\t{\n\t\t\t\t\titems.map((item, index) => (\n\t\t\t\t\t\t<li key={index}>\n\t\t\t\t\t\t\t<a href={`/${item.toLowerCase()}`}>{item}</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t))\n\t\t\t\t}\n\t\t\t</ul>\n\n\t\t\t<p>\n\t\t\t\t{items.length > 0 ? `Found ${items.length} technologies` : 'No technologies found'}\n\t\t\t</p>\n\n\t\t\t<div\n\t\t\t\tdata-count={count}\n\t\t\t\tdata-env={import.meta.env.MODE}\n\t\t\t\tclass:list={['card', { highlight: count > 5 }]}\n\t\t\t>\n\t\t\t\t<p>Interactive card with dynamic attributes</p>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<script>\n\t\t\tinterface Config {\n\t\t\t\ttheme: 'light' | 'dark';\n\t\t\t\tdebug: boolean;\n\t\t\t}\n\n\t\t\tconst config: Config = {\n\t\t\t\ttheme: 'dark',\n\t\t\t\tdebug: false\n\t\t\t};\n\n\t\t\tconst initializeApp = async (): Promise<void> => {\n\t\t\t\tconsole.log('Astro app initialized');\n\n\t\t\t\tconst data = await fetch('/api/data')\n\t\t\t\t\t.then((res) => res.json())\n\t\t\t\t\t.catch((err) => console.error('Fetch error:', err));\n\n\t\t\t\tconst count = data?.items?.length ?? 0;\n\t\t\t\tconsole.log(`Found ${count} items`);\n\t\t\t};\n\n\t\t\tdocument.addEventListener('DOMContentLoaded', () => {\n\t\t\t\tinitializeApp();\n\t\t\t});\n\t\t</script>\n\t</body>\n</html>\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n\n#define MAX_NAME_LENGTH 100\n#define BUFFER_SIZE 1024\n\n\n/* Constants for configuration limits */\n#define MIN_TIMEOUT 1000\n#define MAX_TIMEOUT 10000\n#define MAX_RETRIES 5\n\n/**\n * HelloWorld structure represents a greeter object with configuration\n * Contains:\n * - name: String identifier for the greeter (max 100 chars)\n * - created_at: Timestamp when instance was created\n * - timeout: Milliseconds to wait between greetings (1000-10000)\n * - retries: Number of retry attempts (0-5)\n */\ntypedef struct {\n    char name[MAX_NAME_LENGTH];\n    time_t created_at;\n    int timeout;\n    int retries;\n} HelloWorld;\n\nHelloWorld* hello_world_create(const char* name) {\n    HelloWorld* hw = (HelloWorld*)malloc(sizeof(HelloWorld));\n    if (!hw) return NULL;\n    \n    strncpy(hw->name, name, MAX_NAME_LENGTH - 1);\n    hw->name[MAX_NAME_LENGTH - 1] = '\\0';\n    hw->created_at = time(NULL);\n    hw->timeout = 5000;\n    hw->retries = 3;\n    \n    return hw;\n}\n\nvoid hello_world_destroy(HelloWorld* hw) {\n    if (hw) {\n        free(hw);\n    }\n}\n\nvoid hello_world_greet(HelloWorld* hw, const char** names, int count) {\n    for (int i = 0; i < count; i++) {\n        printf(\"Hello, %s from %s!\\n\", names[i], hw->name);\n    }\n}\n\nvoid hello_world_configure(HelloWorld* hw, int timeout, int retries) {\n    hw->timeout = timeout;\n    hw->retries = retries;\n}\n\nchar* hello_world_generate_report(const HelloWorld* hw) {\n    char* report = (char*)malloc(BUFFER_SIZE);\n    char time_str[26];\n    ctime_r(&hw->created_at, time_str);\n    time_str[24] = '\\0';\n    \n    snprintf(report, BUFFER_SIZE,\n        \"HelloWorld Report\\n\"\n        \"================\\n\"\n        \"Name: %s\\n\"\n        \"Created: %s\\n\"\n        \"Timeout: %d\\n\"\n        \"Retries: %d\\n\",\n        hw->name, time_str, hw->timeout, hw->retries);\n    \n    return report;\n}\n\nint main() {\n    HelloWorld* greeter = hello_world_create(\"C Example\");\n    \n    const char* names[] = {\"Alice\", \"Bob\"};\n    int names_count = sizeof(names) / sizeof(names[0]);\n    \n    hello_world_configure(greeter, 1000, 5);\n    hello_world_greet(greeter, names, names_count);\n    \n    char* report = hello_world_generate_report(greeter);\n    printf(\"%s\\n\", report);\n    free(report);\n    \n    hello_world_destroy(greeter);\n    return 0;\n}\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Default timeout duration for operations\nconst timeout = 5 * time.Second\n\nvar (\n\tinstanceCount int\n\tmu           sync.RWMutex\n)\n\n/**\n * HelloWorld represents a greeter with configuration options\n * Contains:\n * - name: String identifier for the greeter\n * - createdAt: Timestamp when instance was created\n * - options: Map of configuration options\n */\ntype HelloWorld struct {\n\tname      string\n\tcreatedAt time.Time\n\toptions   map[string]interface{}\n}\n\ntype Config struct {\n\tTimeout  time.Duration `json:\"timeout\"`\n\tRetries  int          `json:\"retries\"`\n\tDebug    bool         `json:\"debug\"`\n}\n\nfunc NewHelloWorld(name string) *HelloWorld {\n\tmu.Lock()\n\tinstanceCount++\n\tmu.Unlock()\n\treturn &HelloWorld{\n\t\tname:      name,\n\t\tcreatedAt: time.Now(),\n\t\toptions:   make(map[string]interface{}),\n\t}\n}\n\nfunc (h *HelloWorld) Greet(ctx context.Context, names ...string) error {\n\tfor _, name := range names {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tdefault:\n\t\t\tfmt.Printf(\"Hello, %s!\\n\", name)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (h *HelloWorld) Configure(cfg Config) {\n\th.options[\"timeout\"] = cfg.Timeout\n\th.options[\"retries\"] = cfg.Retries\n\th.options[\"debug\"] = cfg.Debug\n}\n\nfunc (h *HelloWorld) generateReport() string {\n\tdata, _ := json.MarshalIndent(h.options, \"\", \"  \")\n\treturn fmt.Sprintf(`\n\t\tHelloWorld Report\n\t\t================\n\t\tName: %s\n\t\tCreated: %s\n\t\tOptions: %s\n\t`, h.name, h.createdAt.Format(time.RFC3339), string(data))\n}\n\nfunc main() {\n\tctx, cancel := context.WithTimeout(context.Background(), timeout)\n\tdefer cancel()\n\n\tgreeter := NewHelloWorld(\"Go\")\n\tgreeter.Configure(Config{\n\t\tTimeout: timeout,\n\t\tRetries: 3,\n\t\tDebug:   true,\n\t})\n\n\tif err := greeter.Greet(ctx, \"Alice\", \"Bob\"); err != nil {\n\t\tfmt.Printf(\"Error greeting: %v\\n\", err)\n\t}\n\tfmt.Println(greeter.generateReport())\n}\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.html",
    "content": "<article>\n    <h1>A simple HTML document</h1>\n    <div>\n        Here is a test in div.\n        <p>\n            This is a paragraph inside a div element, have\n            <mention>@Mention Tag</mention>\n            <a href=\"https://google.com\"\n                >Link with: <b>Bold <i>italic</i></b></a\n            >, <strong>bold</strong>, <em>italic</em>, and\n            <code>code</code> text.\n        </p>\n        <script>\n            console.log(\"Hello, world!\");\n        </script>\n        <p>This is a text before blockquote.</p>\n        <blockquote>\n            This is before paragraph in blockquote.\n            <p>This is first blockquote paragraph.</p>\n            <blockquote>\n                <p>This is second level</p>\n                <blockquote>\n                    <p>This is third level</p>\n                </blockquote>\n\n                <ul>\n                    <li>List item in a blockquote.</li>\n                    <li>Second list item</li>\n                </ul>\n            </blockquote>\n            This is after paragraph in blockquote.\n        </blockquote>\n        <style type=\"text/css\">\n            .highlight {\n                background-color: #ff0;\n            }\n        </style>\n        <p>\n            <img\n                src=\"https://is1-ssl.mzstatic.com/image/thumb/avICmr1PbBRB-PAeplGreA/1378x774.jpg\"\n                height=\"400px\"\n            />\n        </p>\n        <div>\n            <p>This is second paragraph.</p>\n        </div>\n        A text after div.\n        <p>\n            这是一个中文演示段落，用于展示更多的\n            <a href=\"https://github.github.com/gfm/\">Markdown GFM</a>\n            内容。これは日本語のデモ段落です。の多言語サポートを示すためのテキストが含まれています。\n        </p>\n    </div>\n\n    <div>\n        <h2>List</h2>\n        Example for Bulleted and Numbered List:\n\n        <h3>Numbered List</h3>\n        Text before the Numbered List.\n        <ol>\n            <li>\n                Numbered item 1\n                <ol>\n                    <li>Sub <strong>item</strong> 1</li>\n                    <li>Sub <foo>item</foo> 2</li>\n                </ol>\n            </li>\n            <li>Numbered <em>item</em> 2</li>\n            <li>Numbered item 3</li>\n        </ol>\n        Text after the Numbered List.\n        <h3>Bulleted List</h3>\n        Text before the Bulleted List.\n        <ul>\n            <li>\n                Bullet 1\n                <ol>\n                    <li>Sub Numbered 1</li>\n                    <li>Sub Numbered 2</li>\n                </ol>\n            </li>\n            <li>Bullet 2</li>\n        </ul>\n        Text after the Bulleted List.\n    </div>\n    Text before the section.\n    <section>\n        <h2>Table</h2>\n        Text before the table.\n        <table>\n            <thead>\n                <tr>\n                    <th>Head 1</th>\n                    <th>Head 2</th>\n                </tr>\n            </thead>\n            <tbody>\n                <tr>\n                    <td span=\"2\">This <strong>Cell</strong> have 2 span</td>\n                </tr>\n                <tr>\n                    <td>Cell 3</td>\n                    <td>Cell 4</td>\n                </tr>\n            </tbody>\n        </table>\n        Text after the table.\n    </section>\n    Text after the section.\n    <section>\n        <h2>Images</h2>\n        <img\n            src=\"https://is1-ssl.mzstatic.com/image/thumb/5tQkYfzU9bSMUol0GajO4w/1378x774.jpg\"\n            height=\"400px\"\n        />\n        <p>\n            (A Tesla Model X on display at the June 2024 Shanghai new energy\n            vehicle show. Image credit: CnEVPost)\n        </p>\n        <img\n            src=\"https://miro.medium.com/v2/resize:fit:1400/format:webp/1*-Y9ozbNWSViiCmal1TT32w.jpeg\"\n            width=\"100%\"\n        />\n        Text before the image.\n        <img\n            src=\"https://miro.medium.com/v2/resize:fit:1400/format:webp/0*u4La03Nh6E4zIc9-.jpeg\"\n            width=\"100%\"\n        />\n        Text after the image.\n        <img\n            src=\"https://miro.medium.com/v2/resize:fit:1400/format:webp/0*Q_JiltniByWLWoUv\"\n            style=\"width: 100%\"\n        />\n    </section>\n</article>\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.js",
    "content": "const fs = require(\"fs\");\n\nconst getName = function () {\n  return \"John Doe\";\n};\n\n/**\n * A class representing a HelloWorld greeter with various utility methods\n * @class HelloWorld\n * @param {string} name - The name to use for greetings\n */\nclass HelloWorld {\n  // Version number of the HelloWorld class\n  static VERSION = \"1.0.0\";\n  // Counter to track number of class instances\n  static #instanceCount = 0;\n\n  // Private instance fields\n  #name;\n  #options;\n  #createdAt;\n\n  /**\n   * Creates a new HelloWorld instance\n   * @param {string} name - The name to use for greetings\n   * @param {Object} options - Configuration options\n   */\n  constructor(name = \"World\", options = {}) {\n    this.#name = name;\n    this.#options = options;\n    this.#createdAt = new Date();\n    HelloWorld.#instanceCount++;\n  }\n\n  static getInstanceCount() {\n    return HelloWorld.#instanceCount;\n  }\n\n  get name() {\n    return this.#name;\n  }\n\n  set name(value) {\n    this.#name = value;\n  }\n\n  async greet(...names) {\n    try {\n      for (const name of names) {\n        await new Promise((resolve) => setTimeout(resolve, 100));\n        console.log(`Hello, ${name}!`);\n      }\n    } catch (error) {\n      console.error(`Error: ${error.message}`);\n    }\n  }\n\n  configure(options = {}) {\n    Object.assign(this.#options, options);\n  }\n\n  *generateSequence(start = 0, end = 10) {\n    for (let i = start; i <= end; i++) yield i;\n  }\n\n  processNames(names) {\n    return names\n      .filter((name) => name.length > 0)\n      .map((name) => name.toUpperCase())\n      .sort();\n  }\n}\n\nconst greeter = new HelloWorld(\"JavaScript\");\n\n(async () => {\n  const uniqueNames = new Set([\"Alice\", \"Bob\"]);\n  await greeter.greet(...uniqueNames);\n\n  for (const num of greeter.generateSequence(0, 5)) {\n    console.log(num);\n  }\n})();\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.json",
    "content": "[\n  {\n    \"name\": \"GPUI Component\",\n    \"description\": \"UI components for building fantastic desktop application by using GPUI.\",\n    \"license\": \"Apache-2.0\",\n    \"keywords\": [\"UI\", \"desktop\", \"application\"],\n    \"stars\": 3000,\n    \"rgb\": \"rgb(100, 200, 100)\",\n    \"hsla\": \"hsla(20, 100%, 50%, .5)\",\n    \"hsl\": \"hsl(225, 100%, 70%)\",\n    \"中文\": \"#EEAAFF\",\n    \"public\": true,\n    \"repository\": \"https://github.com/longbridge/gpui-component\"\n  }\n]\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.kt",
    "content": "package hello\n\nimport kotlin.math.roundToInt\n\nconst val VERSION = \"1.0.0\"\n\ndata class Config(\n    val timeout: Int = 5000,\n    val retries: Int = 3,\n    val debug: Boolean = false,\n)\n\nenum class LogLevel(val label: String) {\n    INFO(\"INFO\"), WARN(\"WARN\"), ERROR(\"ERROR\");\n    companion object {\n        fun fromString(s: String): LogLevel? = entries.find { it.label == s }\n    }\n}\n\nsealed class Result<out T> {\n    data class Success<T>(val data: T) : Result<T>()\n    data class Error(val message: String) : Result<Nothing>()\n}\n\ninterface Greetable {\n    fun greet(vararg names: String): List<String>\n}\n\nclass HelloWorld(private val name: String = \"World\") : Greetable {\n    private var config = Config()\n    private val createdAt = System.currentTimeMillis()\n\n    override fun greet(vararg names: String): List<String> {\n        return names.map { \"Hello, $it!\" }.also { lines ->\n            if (config.debug) lines.forEach { println(\"  [debug] $it\") }\n        }\n    }\n\n    fun configure(block: Config.() -> Config) {\n        config = config.block()\n    }\n\n    fun processNames(names: List<String>?): List<String> =\n        names?.filter { it.isNotBlank() }\n            ?.map { it.uppercase() }\n            ?.sorted()\n            ?: emptyList()\n\n    fun generateReport(): String {\n        val elapsed = ((System.currentTimeMillis() - createdAt) / 1000.0).roundToInt()\n        return \"\"\"\n            |HelloWorld Report\n            |=================\n            |Name: $name\n            |Elapsed: ${elapsed}s\n            |Config: timeout=${config.timeout}, retries=${config.retries}\n        \"\"\".trimMargin()\n    }\n\n    override fun toString(): String = \"HelloWorld(name=$name)\"\n}\n\nfun <T> safely(block: () -> T): Result<T> = try {\n    Result.Success(block())\n} catch (e: Exception) {\n    Result.Error(e.message ?: \"unknown error\")\n}\n\nfun describe(obj: Any?): String = when (obj) {\n    null -> \"null\"\n    is String -> \"String(${obj.length}): \\\"${obj.take(20)}\\\"\"\n    is Result.Success<*> -> \"ok: ${obj.data}\"\n    is Result.Error -> \"err: ${obj.message}\"\n    else -> obj::class.simpleName ?: \"unknown\"\n}\n\nfun main() {\n    val greeter = HelloWorld(\"Kotlin\")\n\n    greeter.configure { copy(debug = true, retries = 5) }\n    greeter.greet(\"Alice\", \"Bob\", \"Charlie\")\n\n    val processed = greeter.processNames(listOf(\"alice\", \"\", \"bob\"))\n    println(\"Processed: $processed\")\n\n    val result = safely { greeter.generateReport() }\n    when (result) {\n        is Result.Success -> println(result.data)\n        is Result.Error -> println(\"Failed: ${result.message}\")\n    }\n\n    // Null safety & describe\n    val items: List<Any?> = listOf(\"hello\", 42, null, result)\n    items.forEach { println(\"  ${describe(it)}\") }\n\n    println(\"Instances: $greeter (v$VERSION)\")\n}\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.lua",
    "content": "--[[\n    HelloWorld module demonstrating Lua syntax highlighting\n    Version: 1.0.0\n--]]\n\nlocal VERSION = \"1.0.0\"\n\n-- Module configuration with default values\nlocal Config = {\n    timeout = 5000,\n    retries = 3,\n    debug = false\n}\n\n-- Enum-like table for log levels\nlocal LogLevel = {\n    INFO = \"INFO\",\n    WARN = \"WARN\",\n    ERROR = \"ERROR\"\n}\n\n-- Create a class using metatables\nlocal HelloWorld = {}\nHelloWorld.__index = HelloWorld\n\nfunction HelloWorld.new(name)\n    local self = setmetatable({}, HelloWorld)\n    self.name = name or \"World\"\n    self.config = {timeout = 5000, retries = 3, debug = false}\n    self.createdAt = os.time()\n    self._greetCount = 0  -- private-like field\n    return self\nend\n\n-- Method using colon syntax (implicit self)\nfunction HelloWorld:greet(...)\n    local names = {...}\n    local results = {}\n\n    for i, name in ipairs(names) do\n        local greeting = string.format(\"Hello, %s!\", name)\n        table.insert(results, greeting)\n        self._greetCount = self._greetCount + 1\n\n        if self.config.debug then\n            print(string.format(\"  [debug] %s\", greeting))\n        end\n    end\n\n    return results\nend\n\nfunction HelloWorld:configure(newConfig)\n    for k, v in pairs(newConfig) do\n        self.config[k] = v\n    end\nend\n\nfunction HelloWorld:processNames(names)\n    local processed = {}\n\n    for _, name in ipairs(names or {}) do\n        if name ~= \"\" and name:match(\"%S\") then\n            table.insert(processed, name:upper())\n        end\n    end\n\n    table.sort(processed)\n    return processed\nend\n\nfunction HelloWorld:generateReport()\n    local elapsed = os.time() - self.createdAt\n    local lines = {\n        \"HelloWorld Report\",\n        \"=================\",\n        string.format(\"Name: %s\", self.name),\n        string.format(\"Elapsed: %ds\", elapsed),\n        string.format(\"Greetings: %d\", self._greetCount),\n        string.format(\"Config: timeout=%d, retries=%d\",\n            self.config.timeout, self.config.retries)\n    }\n    return table.concat(lines, \"\\n\")\nend\n\n-- Metatable for Result type (Success/Error)\nlocal function Success(data)\n    return {success = true, data = data}\nend\n\nlocal function Error(message)\n    return {success = false, message = message}\nend\n\n-- Protected call wrapper\nlocal function safely(fn)\n    local status, result = pcall(fn)\n    return status and Success(result) or Error(tostring(result))\nend\n\n-- Pattern matching-like function\nlocal function describe(obj)\n    if obj == nil then return \"nil\"\n    elseif type(obj) == \"string\" then\n        return string.format('String(%d): \"%s\"', #obj, obj:sub(1, 20))\n    elseif type(obj) == \"table\" and obj.success ~= nil then\n        return obj.success and (\"ok: \" .. tostring(obj.data))\n                           or (\"err: \" .. obj.message)\n    else\n        return type(obj)\n    end\nend\n\n-- Main execution\nlocal greeter = HelloWorld.new(\"Lua\")\n\ngreeter:configure({debug = true, retries = 5})\ngreeter:greet(\"Alice\", \"Bob\", \"Charlie\")\n\nlocal processed = greeter:processNames({\"alice\", \"\", \"bob\", \"  charlie  \"})\nprint(\"Processed: \" .. table.concat(processed, \", \"))\n\nlocal result = safely(function() return greeter:generateReport() end)\nif result.success then\n    print(result.data)\nelse\n    print(\"Failed: \" .. result.message)\nend\n\nprint(string.format(\"\\nVersion: %s\", VERSION))\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.md",
    "content": "# Hello, **World**!\n\nBuild Status [![Build Status](https://github.com/longbridge/gpui-component/actions/workflows/ci.yml/badge.svg)](https://github.com/longbridge/gpui-component/actions/workflows/ci.yml) of [GPUI Component](https://github.com/longbridge/gpui-component).\n\nThis is first paragraph, there have **BOLD**, _italic_, and ~strikethrough~, `code` text [^1] [^2].\n\nThis is an additional demonstration paragraph in English demonstrating more content for [Markdown GFM]. It includes various stylistic elements and plain text.\n\n![Img](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*WgEz5f3n3lD7MfC7NeQGOA.jpeg)\n\n这是一个中文演示段落，用于展示更多的 [Markdown GFM] 内容。您可以在此尝试使用使用**粗体**、*斜体*和`代码`等样式。これは日本語のデモ段落です。Markdown の多言語サポートを示すためのテキストが含まれています。例えば、、**ボールド**、_イタリック_、および`コード`のスタイルなどを試すことができます。\n\n[Markdown GFM]: https://github.github.com/gfm/\n\n[^1]: This is a footnote example.\n\n[^2]: Here is another footnote.\n\n## Basic formatting\n\n### **Bold** text\n\nYou can mark some text as bold with **two asterisks**\nor **two underscores**.\n\n### **Italic** text\n\nYou can mark some text as italic with _asterisks_\nor _underscores_.\n\n### **_Bold and italic_**\n\nThree stars gives **_bold and italic_**\n\n### ~~Strikethrough~~\n\nUsing `~~two tildes~~` will strikethrough: ~~two tildes~~\n\n## Blockquotes\n\n> Blockquote: More complex nested inline style like **bold: _italic_**.\n> This is second paragraph, it includes a block quote.\n\nAnd this is next blockquote\n\n> Hello, world!\n\n### Nested blockquotes\n\n> First level\n>\n> > Second level\n> > Third level\n>\n> ```rs\n> const FOO: &str = \"bar\";\n> ```\n\n## Code block\n\n#### Rust\n\n```rust\nstruct Repository {\n    /// Name of the repository.\n    name: String,\n}\n\nfn main() {\n    let _ = Repository {\n        name: \"GPUI Component\".to_string(),\n    };\n\n    println!(\"Hello, World!\");\n}\n```\n\n#### Python\n\n```python\nclass Repository:\n    \"\"\"A repository.\"\"\"\n\n    def __init__(self, name: str):\n        \"\"\"Initialize the repository.\n\n        Args:\n            name: Name of the repository.\n        \"\"\"\n        self.name = name\n```\n\n---\n\n## Heading for [Links](https://www.google.com)\n\nHere is a link to [Google](https://www.google.com), and another to [Rust](https://www.rust-lang.org).\n\n## Image\n\n![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*sOTh1aAl32jxKNuGO0TOcA.png)\n\n### SVG\n\n![Rust](https://rust-lang.org/static/images/rust-logo-blk.svg)\n\n## Table\n\n| Header 1 | Centered | Header 3                             | Align Right |\n| -------- | :------: | ------------------------------------ | ----------: |\n| Cell 0   |  Cell 1  | This is a long cell with line break. |      Cell 3 |\n| Row 2    |  Row 2   | Row 2<br>[Link](https://github.com)  |       Row 2 |\n| Row 3    | **Bold** | Row 3                                |       Row 3 |\n\nSee the way the text is aligned, depending on the position of `':'`\n\n| Syntax    | Description |   Test Text |\n| :-------- | :---------: | ----------: |\n| Header    |    Title    | Here's this |\n| Paragraph |    Text     |    And more |\n\n## Lists\n\n### Bulleted List\n\n- Bullet 1, this is very long and needs to be wrapped to the next line, display should be wrapped to the next line as well.\n- Bullet 2, the second bullet item is also long and needs to be wrapped to the next line.\n  - Bullet 2.1\n    - Bullet 2.1.1\n      - Bullet 2.1.1.1\n    - Bullet 2.1.2\n  - Bullet 2.2\n- Bullet 3\n\n### Numbered List\n\n1. Numbered item 1\n   1. Numbered item 1.1\n      1. Numbered item 1.1.1\n   1. Numbered item 1.2\n2. Numbered item 2\n3. Numbered item 3\n\n### To-Do List\n\n- [x] Task 1, a long long text task, this line is very long and needs to be wrapped to the next line, display should be wrapped to the next line as well.\n- [ ] Task 2, going to do something if there is a long text that needs to be wrapped to the next line.\n- [ ] Task 3\n\n## Heading\n\nAdd `##` at the beginning of a line to set as Heading.\nYou can use up to 6 `#` symbols for the corresponding Heading levels\n\n## Heading 2\n\nThis is paragraph of the heading 2.\n\n### Heading 3\n\nThis is paragraph of the heading 3.\n\n#### Heading 4\n\nThis is paragraph of the heading 4.\n\n##### Heading 5\n\nThis is paragraph of the heading 5.\n\n###### Heading 6\n\nThis is paragraph of the heading 6.\n\n## HTML\n\n### Paragraph and Text\n\n<div>\n    Here is a test in div.\n    <p>This is a paragraph inside a div element, have <a href=\"https://google.com\">link</a>, <strong>bold</strong>, <em>italic</em>, and <code>code</code> text.</p>\n    <div>\n        <p>This is second paragraph.</p>\n    </div>\n    A text after div.\n</div>\n\n### List\n\n<ol>\n<li>Numbered item 1</li>\n<li>Numbered item 2</li>\n</ol>\n\n<ul>\n<li>Bullet 1</li>\n<li>Bullet 2</li>\n</ul>\n\n### Table\n\n<table>\n<thead>\n<tr>\n<td>Head 1</td>\n<td>Head 2</td>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Cell</strong> 1</td>\n<td>Cell 2</td>\n</tr>\n<tr>\n<td>Cell 3</td>\n<td>Cell 4</td>\n</tr>\n</tbody>\n</table>\n\n### Image\n\n<img src=\"https://miro.medium.com/v2/resize:fit:1400/format:webp/1*QY36p64kSGfBQsIFci8WBw.png\" alt=\"The Best Programming Languages to Learn in 2025\" width=\"100%\" />\n\n## Unsupported\n\n### HTML\n\n<details>\n<summary>Click to expand</summary>\n<div>\n    <p>This is a paragraph <a href=\"https://google.com\">inside</a> a details element.</p>\n    <p>This is second paragraph.</p>\n</div>\n</details>\n\n### Math\n\nThis is an inline math $x^2 + y^2 = z^2$.\n\nThis is a block math:\n\n$$\n\\begin{aligned}\nx^2 + y^2 &= z^2 \\\\\nx^3 + y^3 &= z^3\n\\end{aligned}\n$$\n\nThis is final paragraph, it includes a code block and a list of items.\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.nv",
    "content": "use std.net.http.client.{HttpClient, Request};\nuse std.net.http.OK;\n\nfn main() throws {\n    let client = HttpClient.new(\n        max_redirect_count: 5,\n        user_agent: \"navi-client\",\n    );\n    let req = try Request.get(\"https://httpbin.org/get\");\n    let res = try client.request(req);\n\n    if (res.status() != OK) {\n        try println(\"Failed to fetch repo\", res.text());\n        return;\n    }\n\n    try println(res.text());\n}\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.php",
    "content": "<?php\n\n// This file is only used for syntax highlighting fixtures.\n// Intentionally small and dependency-free.\n\ndeclare(strict_types=1);\n\nfinal class HelloWorld\n{\n    public const VERSION = '1.0.0';\n\n    public function __construct(private string $name = 'World')\n    {\n    }\n\n    public function greet(string ...$names): array\n    {\n        return array_map(fn(string $n): string => \"Hello, {$n}!\", $names);\n    }\n\n    public function report(): string\n    {\n        return \"HelloWorld({$this->name})\";\n    }\n}\n\nfunction is_valid_email(string $email): bool\n{\n    return preg_match('/^[\\w+\\-.]+@[\\w\\-]+\\.[a-z]{2,}$/i', $email) === 1;\n}\n\n$name = $_GET['name'] ?? 'PHP';\n$email = $_POST['email'] ?? 'user@example.com';\n\n$greeter = new HelloWorld((string) $name);\n$lines = $greeter->greet('Alice', 'Bob', 'Charlie');\n\n?>\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <title><?php echo htmlspecialchars($greeter->report(), ENT_QUOTES, 'UTF-8'); ?></title>\n</head>\n<body>\n  <h1><?php echo htmlspecialchars($greeter->report(), ENT_QUOTES, 'UTF-8'); ?></h1>\n\n  <ul>\n    <?php foreach ($lines as $line): ?>\n      <li><?php echo htmlspecialchars($line, ENT_QUOTES, 'UTF-8'); ?></li>\n    <?php endforeach; ?>\n  </ul>\n\n  <form method=\"post\">\n    <label>\n      Email:\n      <input name=\"email\" value=\"<?php echo htmlspecialchars((string) $email, ENT_QUOTES, 'UTF-8'); ?>\" />\n    </label>\n    <button type=\"submit\">Validate</button>\n  </form>\n\n  <?php if ($email !== ''): ?>\n    <p>Status: <strong><?php echo is_valid_email((string) $email) ? 'valid' : 'invalid'; ?></strong></p>\n  <?php endif; ?>\n</body>\n</html>\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.py",
    "content": "from __future__ import annotations\nfrom typing import Optional, List, Dict, Any\nfrom dataclasses import dataclass\nfrom datetime import datetime\nimport json\nimport asyncio\n\n# Data class for configuration settings\n@dataclass\nclass Config:\n    timeout: int = 5000  # Default timeout in milliseconds\n    retries: int = 3     # Number of retry attempts\n    debug: bool = False  # Debug mode flag\n\n\"\"\"\nHelloWorld class provides greeting functionality with configuration options.\n\nFeatures:\n- Async greetings with customizable names\n- Configuration management\n- Instance tracking\n- Report generation\n\nExample:\n    greeter = HelloWorld(\"Python\")\n    await greeter.greet(\"Alice\", \"Bob\")\n\"\"\"\nclass HelloWorld:\n    VERSION: str = \"1.0.0\"\n    _instance_count: int = 0\n    \n    def __init__(self, name: str = \"World\", options: Optional[Dict[str, Any]] = None):\n        self._name = name\n        self._options = options or {}\n        self._created_at = datetime.now()\n        self._config = Config()\n        HelloWorld._instance_count += 1\n    \n    @property\n    def name(self) -> str:\n        return self._name\n    \n    @name.setter\n    def name(self, value: str) -> None:\n        if not value:\n            raise ValueError(\"Name cannot be empty\")\n        self._name = value\n    \n    @classmethod\n    def get_instance_count(cls) -> int:\n        return cls._instance_count\n    \n    async def greet(self, *names: str) -> None:\n        try:\n            for name in names:\n                await asyncio.sleep(0.1)\n                print(f\"Hello, {name}!\")\n        except Exception as e:\n            print(f\"Error: {str(e)}\")\n    \n    def process_names(self, names: List[str] = None) -> List[str]:\n        if names is None:\n            names = []\n        return sorted([name.upper() for name in names if name])\n    \n    def _generate_report(self) -> str:\n        return f\"\"\"\n        HelloWorld Report\n        ================\n        Name: {self._name}\n        Created: {self._created_at.isoformat()}\n        Options: {json.dumps(self._options, indent=2)}\n        \"\"\"\n    \n    def __str__(self) -> str:\n        return f\"HelloWorld(name={self._name})\"\n\nasync def main():\n    greeter = HelloWorld(\"Python\")\n    await greeter.greet(\"Alice\", \"Bob\", \"Charlie\")\n    print(greeter._generate_report())\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.rb",
    "content": "require 'json'\nrequire 'date'\n\n# Module for logging functionality\nmodule Logging\n  LOG_LEVELS = %i[debug info warn error].freeze\nend\n\n# HelloWorld class provides greeting functionality with configuration options\n# @author Example Author\n# @version 1.0.0\n# Features:\n# - Configurable greetings with multiple names\n# - Instance tracking\n# - Report generation\n# - Logging capabilities\nclass HelloWorld < Object\n  include Logging\n\n  @@instances = 0\n  VERSION = '1.0.0'\n\n  attr_accessor :name\n  attr_reader :created_at\n\n  def initialize(name: 'World', options: {})\n    @name = name\n    @created_at = Time.now\n    @options = options\n    @@instances += 1\n    yield self if block_given?\n  end\n\n  def self.instance_count(format: :short)\n    case format\n    when :short then @@instances.to_s\n    when :long then \"Total instances: #{@@instances}\"\n    end\n  end\n\n  def greet(*names)\n    names.each { |n| puts \"Hello, #{n}!\" }\n  rescue => e\n    puts \"Error: #{e.message}\"\n  end\n\n  def configure(timeout: 5000, retries: 3)\n    @options.merge!(timeout: timeout, retries: retries)\n  end\n\n  def configured?\n    !@options.empty?\n  end\n\n  def process_names(names)\n    names.map(&:upcase).select(&:present?)\n  end\n\n  private\n\n  def generate_report\n    <<~REPORT\n      HelloWorld Report\n      ================\n      Name: #{@name}\n      Created: #{@created_at}\n      Options: #{@options.to_json}\n    REPORT\n  end\nend\n\n# Create new greeter instance with configuration block\ngreeter = HelloWorld.new(name: 'Ruby') { |g| g.configure(timeout: 1000) }\n\n# Process array and handle errors\nnumbers = [1, 2, 3, 4, 5]\ndoubled = numbers.map { |n| n * 2 }\n\n# Email validation\nEMAIL_REGEX = /\\A[\\w+\\-.]+@[a-z\\d\\-]+(\\.[a-z\\d\\-]+)*\\.[a-z]+\\z/i\nvalidator = ->(email) { email.match?(EMAIL_REGEX) }\n\nbegin\n  greeter.greet('Alice', 'Bob')\nrescue StandardError => e\n  puts \"Error occurred: #{e.message}\"\nensure\n  puts \"Execution completed at #{Time.now}\"\nend\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.rs",
    "content": "use serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::time::Duration;\nuse tokio::time;\n\n// Version number of the HelloWorld struct\nconst VERSION: &str = \"1.0.0\";\n\n/// HelloWorld struct provides greeting functionality with configuration options\n/// This is CJK 中文🎊 for test line, column.\n///\n/// # Features\n/// - Async greetings with customizable names\n/// - Configuration management via HashMap\n/// - Report generation\n/// - Error handling with custom error types\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct HelloWorld {\n    name: String,\n    #[serde(skip)]\n    options: HashMap<String, serde_json::Value>,\n    created_at: chrono::DateTime<chrono::Utc>,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum HelloError {\n    #[error(\"Invalid name: {0}\")]\n    InvalidName(String),\n    #[error(\"Operation timeout\")]\n    Timeout,\n}\n\n// Document colors: #FF0033, #00AA33, #0033FF, #FFAA33\n\ntype Result<T> = std::result::Result<T, HelloError>;\n\nimpl HelloWorld {\n    pub fn new(name: impl Into<String>) -> Self {\n        Self {\n            name: name.into(),\n            options: HashMap::new(),\n            created_at: chrono::Utc::now(),\n        }\n    }\n\n    // Greets multiple people asynchronously with configurable delay\n    //\n    // Use `Command-click` on `Duration` will jump to its definition.\n    // Use `Control-click` on `String`, `HashMap` or `Result` will open its documentation page.\n    pub async fn greet<T: AsRef<str>>(&self, names: &[T]) -> Result<()> {\n        for name in names {\n            time::sleep(Duration::from_millis(100)).await;\n            println!(\"Hello, {}!\", name.as_ref());\n        }\n        Ok(())\n    }\n\n    fn generate_report(&self) -> String {\n        format!(\n            \"HelloWorld Report\\n================\\nName: {}\\nCreated: {}\\nOptions: {:?}\",\n            self.name, self.created_at, self.options\n        )\n    }\n}\n\ntrait Configurable {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>);\n    fn is_configured(&self) -> bool;\n}\n\nimpl Configurable for HelloWorld {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>) {\n        self.options.extend(options);\n    }\n\n    fn is_configured(&self) -> bool {\n        !self.options.is_empty()\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let mut greeter = HelloWorld::new(\"Rust\");\n\n    let mut config = HashMap::new();\n    config.insert(\"timeout\".to_string(), serde_json::json!(5000));\n    config.insert(\"retries\".to_string(), serde_json::json!(3));\n\n    greeter.configure(config);\n\n    match greeter.greet(&[\"Alice\", \"Bob\"]).await {\n        Ok(_) => println!(\"Greetings sent successfully\"),\n        Err(e) => eprintln!(\"Error: {}\", e),\n    }\n\n    Ok(())\n}\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::time::Duration;\nuse tokio::time;\n\n// Version number of the HelloWorld struct\nconst VERSION: &str = \"1.0.0\";\n\n/// HelloWorld struct provides greeting functionality with configuration options\n///\n/// # Features\n/// - Async greetings with customizable names\n/// - Configuration management via HashMap\n/// - Report generation\n/// - Error handling with custom error types\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct HelloWorld {\n    name: String,\n    #[serde(skip)]\n    options: HashMap<String, serde_json::Value>,\n    created_at: chrono::DateTime<chrono::Utc>,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum HelloError {\n    #[error(\"Invalid name: {0}\")]\n    InvalidName(String),\n    #[error(\"Operation timeout\")]\n    Timeout,\n}\n\ntype Result<T> = std::result::Result<T, HelloError>;\n\nimpl HelloWorld {\n    pub fn new(name: impl Into<String>) -> Self {\n        Self {\n            name: name.into(),\n            options: HashMap::new(),\n            created_at: chrono::Utc::now(),\n        }\n    }\n\n    // Greets multiple people asynchronously with configurable delay\n    pub async fn greet<T: AsRef<str>>(&self, names: &[T]) -> Result<()> {\n        for name in names {\n            time::sleep(Duration::from_millis(100)).await;\n            println!(\"Hello, {}!\", name.as_ref());\n        }\n        Ok(())\n    }\n\n    fn generate_report(&self) -> String {\n        format!(\n            \"HelloWorld Report\\n================\\nName: {}\\nCreated: {}\\nOptions: {:?}\",\n            self.name, self.created_at, self.options\n        )\n    }\n}\n\ntrait Configurable {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>);\n    fn is_configured(&self) -> bool;\n}\n\nimpl Configurable for HelloWorld {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>) {\n        self.options.extend(options);\n    }\n\n    fn is_configured(&self) -> bool {\n        !self.options.is_empty()\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let mut greeter = HelloWorld::new(\"Rust\");\n\n    let mut config = HashMap::new();\n    config.insert(\"timeout\".to_string(), serde_json::json!(5000));\n    config.insert(\"retries\".to_string(), serde_json::json!(3));\n\n    greeter.configure(config);\n\n    match greeter.greet(&[\"Alice\", \"Bob\"]).await {\n        Ok(_) => println!(\"Greetings sent successfully\"),\n        Err(e) => eprintln!(\"Error: {}\", e),\n    }\n\n    Ok(())\n}\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::time::Duration;\nuse tokio::time;\n\n// Version number of the HelloWorld struct\nconst VERSION: &str = \"1.0.0\";\n\n/// HelloWorld struct provides greeting functionality with configuration options\n///\n/// # Features\n/// - Async greetings with customizable names\n/// - Configuration management via HashMap\n/// - Report generation\n/// - Error handling with custom error types\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct HelloWorld {\n    name: String,\n    #[serde(skip)]\n    options: HashMap<String, serde_json::Value>,\n    created_at: chrono::DateTime<chrono::Utc>,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum HelloError {\n    #[error(\"Invalid name: {0}\")]\n    InvalidName(String),\n    #[error(\"Operation timeout\")]\n    Timeout,\n}\n\ntype Result<T> = std::result::Result<T, HelloError>;\n\nimpl HelloWorld {\n    pub fn new(name: impl Into<String>) -> Self {\n        Self {\n            name: name.into(),\n            options: HashMap::new(),\n            created_at: chrono::Utc::now(),\n        }\n    }\n\n    // Greets multiple people asynchronously with configurable delay\n    pub async fn greet<T: AsRef<str>>(&self, names: &[T]) -> Result<()> {\n        for name in names {\n            time::sleep(Duration::from_millis(100)).await;\n            println!(\"Hello, {}!\", name.as_ref());\n        }\n        Ok(())\n    }\n\n    fn generate_report(&self) -> String {\n        format!(\n            \"HelloWorld Report\\n================\\nName: {}\\nCreated: {}\\nOptions: {:?}\",\n            self.name, self.created_at, self.options\n        )\n    }\n}\n\ntrait Configurable {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>);\n    fn is_configured(&self) -> bool;\n}\n\nimpl Configurable for HelloWorld {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>) {\n        self.options.extend(options);\n    }\n\n    fn is_configured(&self) -> bool {\n        !self.options.is_empty()\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let mut greeter = HelloWorld::new(\"Rust\");\n\n    let mut config = HashMap::new();\n    config.insert(\"timeout\".to_string(), serde_json::json!(5000));\n    config.insert(\"retries\".to_string(), serde_json::json!(3));\n\n    greeter.configure(config);\n\n    match greeter.greet(&[\"Alice\", \"Bob\"]).await {\n        Ok(_) => println!(\"Greetings sent successfully\"),\n        Err(e) => eprintln!(\"Error: {}\", e),\n    }\n\n    Ok(())\n}\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::time::Duration;\nuse tokio::time;\n\n// Version number of the HelloWorld struct\nconst VERSION: &str = \"1.0.0\";\n\n/// HelloWorld struct provides greeting functionality with configuration options\n///\n/// # Features\n/// - Async greetings with customizable names\n/// - Configuration management via HashMap\n/// - Report generation\n/// - Error handling with custom error types\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct HelloWorld {\n    name: String,\n    #[serde(skip)]\n    options: HashMap<String, serde_json::Value>,\n    created_at: chrono::DateTime<chrono::Utc>,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum HelloError {\n    #[error(\"Invalid name: {0}\")]\n    InvalidName(String),\n    #[error(\"Operation timeout\")]\n    Timeout,\n}\n\ntype Result<T> = std::result::Result<T, HelloError>;\n\nimpl HelloWorld {\n    pub fn new(name: impl Into<String>) -> Self {\n        Self {\n            name: name.into(),\n            options: HashMap::new(),\n            created_at: chrono::Utc::now(),\n        }\n    }\n\n    // Greets multiple people asynchronously with configurable delay\n    pub async fn greet<T: AsRef<str>>(&self, names: &[T]) -> Result<()> {\n        for name in names {\n            time::sleep(Duration::from_millis(100)).await;\n            println!(\"Hello, {}!\", name.as_ref());\n        }\n        Ok(())\n    }\n\n    fn generate_report(&self) -> String {\n        format!(\n            \"HelloWorld Report\\n================\\nName: {}\\nCreated: {}\\nOptions: {:?}\",\n            self.name, self.created_at, self.options\n        )\n    }\n}\n\ntrait Configurable {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>);\n    fn is_configured(&self) -> bool;\n}\n\nimpl Configurable for HelloWorld {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>) {\n        self.options.extend(options);\n    }\n\n    fn is_configured(&self) -> bool {\n        !self.options.is_empty()\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let mut greeter = HelloWorld::new(\"Rust\");\n\n    let mut config = HashMap::new();\n    config.insert(\"timeout\".to_string(), serde_json::json!(5000));\n    config.insert(\"retries\".to_string(), serde_json::json!(3));\n\n    greeter.configure(config);\n\n    match greeter.greet(&[\"Alice\", \"Bob\"]).await {\n        Ok(_) => println!(\"Greetings sent successfully\"),\n        Err(e) => eprintln!(\"Error: {}\", e),\n    }\n\n    Ok(())\n}\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::time::Duration;\nuse tokio::time;\n\n// Version number of the HelloWorld struct\nconst VERSION: &str = \"1.0.0\";\n\n/// HelloWorld struct provides greeting functionality with configuration options\n///\n/// # Features\n/// - Async greetings with customizable names\n/// - Configuration management via HashMap\n/// - Report generation\n/// - Error handling with custom error types\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct HelloWorld {\n    name: String,\n    #[serde(skip)]\n    options: HashMap<String, serde_json::Value>,\n    created_at: chrono::DateTime<chrono::Utc>,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum HelloError {\n    #[error(\"Invalid name: {0}\")]\n    InvalidName(String),\n    #[error(\"Operation timeout\")]\n    Timeout,\n}\n\ntype Result<T> = std::result::Result<T, HelloError>;\n\nimpl HelloWorld {\n    pub fn new(name: impl Into<String>) -> Self {\n        Self {\n            name: name.into(),\n            options: HashMap::new(),\n            created_at: chrono::Utc::now(),\n        }\n    }\n\n    // Greets multiple people asynchronously with configurable delay\n    pub async fn greet<T: AsRef<str>>(&self, names: &[T]) -> Result<()> {\n        for name in names {\n            time::sleep(Duration::from_millis(100)).await;\n            println!(\"Hello, {}!\", name.as_ref());\n        }\n        Ok(())\n    }\n\n    fn generate_report(&self) -> String {\n        format!(\n            \"HelloWorld Report\\n================\\nName: {}\\nCreated: {}\\nOptions: {:?}\",\n            self.name, self.created_at, self.options\n        )\n    }\n}\n\ntrait Configurable {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>);\n    fn is_configured(&self) -> bool;\n}\n\nimpl Configurable for HelloWorld {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>) {\n        self.options.extend(options);\n    }\n\n    fn is_configured(&self) -> bool {\n        !self.options.is_empty()\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let mut greeter = HelloWorld::new(\"Rust\");\n\n    let mut config = HashMap::new();\n    config.insert(\"timeout\".to_string(), serde_json::json!(5000));\n    config.insert(\"retries\".to_string(), serde_json::json!(3));\n\n    greeter.configure(config);\n\n    match greeter.greet(&[\"Alice\", \"Bob\"]).await {\n        Ok(_) => println!(\"Greetings sent successfully\"),\n        Err(e) => eprintln!(\"Error: {}\", e),\n    }\n\n    Ok(())\n}\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::time::Duration;\nuse tokio::time;\n\n// Version number of the HelloWorld struct\nconst VERSION: &str = \"1.0.0\";\n\n/// HelloWorld struct provides greeting functionality with configuration options\n///\n/// # Features\n/// - Async greetings with customizable names\n/// - Configuration management via HashMap\n/// - Report generation\n/// - Error handling with custom error types\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct HelloWorld {\n    name: String,\n    #[serde(skip)]\n    options: HashMap<String, serde_json::Value>,\n    created_at: chrono::DateTime<chrono::Utc>,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum HelloError {\n    #[error(\"Invalid name: {0}\")]\n    InvalidName(String),\n    #[error(\"Operation timeout\")]\n    Timeout,\n}\n\ntype Result<T> = std::result::Result<T, HelloError>;\n\nimpl HelloWorld {\n    pub fn new(name: impl Into<String>) -> Self {\n        Self {\n            name: name.into(),\n            options: HashMap::new(),\n            created_at: chrono::Utc::now(),\n        }\n    }\n\n    // Greets multiple people asynchronously with configurable delay\n    pub async fn greet<T: AsRef<str>>(&self, names: &[T]) -> Result<()> {\n        for name in names {\n            time::sleep(Duration::from_millis(100)).await;\n            println!(\"Hello, {}!\", name.as_ref());\n        }\n        Ok(())\n    }\n\n    fn generate_report(&self) -> String {\n        format!(\n            \"HelloWorld Report\\n================\\nName: {}\\nCreated: {}\\nOptions: {:?}\",\n            self.name, self.created_at, self.options\n        )\n    }\n}\n\ntrait Configurable {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>);\n    fn is_configured(&self) -> bool;\n}\n\nimpl Configurable for HelloWorld {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>) {\n        self.options.extend(options);\n    }\n\n    fn is_configured(&self) -> bool {\n        !self.options.is_empty()\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let mut greeter = HelloWorld::new(\"Rust\");\n\n    let mut config = HashMap::new();\n    config.insert(\"timeout\".to_string(), serde_json::json!(5000));\n    config.insert(\"retries\".to_string(), serde_json::json!(3));\n\n    greeter.configure(config);\n\n    match greeter.greet(&[\"Alice\", \"Bob\"]).await {\n        Ok(_) => println!(\"Greetings sent successfully\"),\n        Err(e) => eprintln!(\"Error: {}\", e),\n    }\n\n    Ok(())\n}\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::time::Duration;\nuse tokio::time;\n\n// Version number of the HelloWorld struct\nconst VERSION: &str = \"1.0.0\";\n\n/// HelloWorld struct provides greeting functionality with configuration options\n///\n/// # Features\n/// - Async greetings with customizable names\n/// - Configuration management via HashMap\n/// - Report generation\n/// - Error handling with custom error types\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct HelloWorld {\n    name: String,\n    #[serde(skip)]\n    options: HashMap<String, serde_json::Value>,\n    created_at: chrono::DateTime<chrono::Utc>,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum HelloError {\n    #[error(\"Invalid name: {0}\")]\n    InvalidName(String),\n    #[error(\"Operation timeout\")]\n    Timeout,\n}\n\ntype Result<T> = std::result::Result<T, HelloError>;\n\nimpl HelloWorld {\n    pub fn new(name: impl Into<String>) -> Self {\n        Self {\n            name: name.into(),\n            options: HashMap::new(),\n            created_at: chrono::Utc::now(),\n        }\n    }\n\n    // Greets multiple people asynchronously with configurable delay\n    pub async fn greet<T: AsRef<str>>(&self, names: &[T]) -> Result<()> {\n        for name in names {\n            time::sleep(Duration::from_millis(100)).await;\n            println!(\"Hello, {}!\", name.as_ref());\n        }\n        Ok(())\n    }\n\n    fn generate_report(&self) -> String {\n        format!(\n            \"HelloWorld Report\\n================\\nName: {}\\nCreated: {}\\nOptions: {:?}\",\n            self.name, self.created_at, self.options\n        )\n    }\n}\n\ntrait Configurable {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>);\n    fn is_configured(&self) -> bool;\n}\n\nimpl Configurable for HelloWorld {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>) {\n        self.options.extend(options);\n    }\n\n    fn is_configured(&self) -> bool {\n        !self.options.is_empty()\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let mut greeter = HelloWorld::new(\"Rust\");\n\n    let mut config = HashMap::new();\n    config.insert(\"timeout\".to_string(), serde_json::json!(5000));\n    config.insert(\"retries\".to_string(), serde_json::json!(3));\n\n    greeter.configure(config);\n\n    match greeter.greet(&[\"Alice\", \"Bob\"]).await {\n        Ok(_) => println!(\"Greetings sent successfully\"),\n        Err(e) => eprintln!(\"Error: {}\", e),\n    }\n\n    Ok(())\n}\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::time::Duration;\nuse tokio::time;\n\n// Version number of the HelloWorld struct\nconst VERSION: &str = \"1.0.0\";\n\n/// HelloWorld struct provides greeting functionality with configuration options\n///\n/// # Features\n/// - Async greetings with customizable names\n/// - Configuration management via HashMap\n/// - Report generation\n/// - Error handling with custom error types\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct HelloWorld {\n    name: String,\n    #[serde(skip)]\n    options: HashMap<String, serde_json::Value>,\n    created_at: chrono::DateTime<chrono::Utc>,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum HelloError {\n    #[error(\"Invalid name: {0}\")]\n    InvalidName(String),\n    #[error(\"Operation timeout\")]\n    Timeout,\n}\n\ntype Result<T> = std::result::Result<T, HelloError>;\n\nimpl HelloWorld {\n    pub fn new(name: impl Into<String>) -> Self {\n        Self {\n            name: name.into(),\n            options: HashMap::new(),\n            created_at: chrono::Utc::now(),\n        }\n    }\n\n    // Greets multiple people asynchronously with configurable delay\n    pub async fn greet<T: AsRef<str>>(&self, names: &[T]) -> Result<()> {\n        for name in names {\n            time::sleep(Duration::from_millis(100)).await;\n            println!(\"Hello, {}!\", name.as_ref());\n        }\n        Ok(())\n    }\n\n    fn generate_report(&self) -> String {\n        format!(\n            \"HelloWorld Report\\n================\\nName: {}\\nCreated: {}\\nOptions: {:?}\",\n            self.name, self.created_at, self.options\n        )\n    }\n}\n\ntrait Configurable {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>);\n    fn is_configured(&self) -> bool;\n}\n\nimpl Configurable for HelloWorld {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>) {\n        self.options.extend(options);\n    }\n\n    fn is_configured(&self) -> bool {\n        !self.options.is_empty()\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let mut greeter = HelloWorld::new(\"Rust\");\n\n    let mut config = HashMap::new();\n    config.insert(\"timeout\".to_string(), serde_json::json!(5000));\n    config.insert(\"retries\".to_string(), serde_json::json!(3));\n\n    greeter.configure(config);\n\n    match greeter.greet(&[\"Alice\", \"Bob\"]).await {\n        Ok(_) => println!(\"Greetings sent successfully\"),\n        Err(e) => eprintln!(\"Error: {}\", e),\n    }\n\n    Ok(())\n}\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::time::Duration;\nuse tokio::time;\n\n// Version number of the HelloWorld struct\nconst VERSION: &str = \"1.0.0\";\n\n/// HelloWorld struct provides greeting functionality with configuration options\n///\n/// # Features\n/// - Async greetings with customizable names\n/// - Configuration management via HashMap\n/// - Report generation\n/// - Error handling with custom error types\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct HelloWorld {\n    name: String,\n    #[serde(skip)]\n    options: HashMap<String, serde_json::Value>,\n    created_at: chrono::DateTime<chrono::Utc>,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum HelloError {\n    #[error(\"Invalid name: {0}\")]\n    InvalidName(String),\n    #[error(\"Operation timeout\")]\n    Timeout,\n}\n\ntype Result<T> = std::result::Result<T, HelloError>;\n\nimpl HelloWorld {\n    pub fn new(name: impl Into<String>) -> Self {\n        Self {\n            name: name.into(),\n            options: HashMap::new(),\n            created_at: chrono::Utc::now(),\n        }\n    }\n\n    // Greets multiple people asynchronously with configurable delay\n    pub async fn greet<T: AsRef<str>>(&self, names: &[T]) -> Result<()> {\n        for name in names {\n            time::sleep(Duration::from_millis(100)).await;\n            println!(\"Hello, {}!\", name.as_ref());\n        }\n        Ok(())\n    }\n\n    fn generate_report(&self) -> String {\n        format!(\n            \"HelloWorld Report\\n================\\nName: {}\\nCreated: {}\\nOptions: {:?}\",\n            self.name, self.created_at, self.options\n        )\n    }\n}\n\ntrait Configurable {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>);\n    fn is_configured(&self) -> bool;\n}\n\nimpl Configurable for HelloWorld {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>) {\n        self.options.extend(options);\n    }\n\n    fn is_configured(&self) -> bool {\n        !self.options.is_empty()\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let mut greeter = HelloWorld::new(\"Rust\");\n\n    let mut config = HashMap::new();\n    config.insert(\"timeout\".to_string(), serde_json::json!(5000));\n    config.insert(\"retries\".to_string(), serde_json::json!(3));\n\n    greeter.configure(config);\n\n    match greeter.greet(&[\"Alice\", \"Bob\"]).await {\n        Ok(_) => println!(\"Greetings sent successfully\"),\n        Err(e) => eprintln!(\"Error: {}\", e),\n    }\n\n    Ok(())\n}\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::time::Duration;\nuse tokio::time;\n\n// Version number of the HelloWorld struct\nconst VERSION: &str = \"1.0.0\";\n\n/// HelloWorld struct provides greeting functionality with configuration options\n///\n/// # Features\n/// - Async greetings with customizable names\n/// - Configuration management via HashMap\n/// - Report generation\n/// - Error handling with custom error types\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct HelloWorld {\n    name: String,\n    #[serde(skip)]\n    options: HashMap<String, serde_json::Value>,\n    created_at: chrono::DateTime<chrono::Utc>,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum HelloError {\n    #[error(\"Invalid name: {0}\")]\n    InvalidName(String),\n    #[error(\"Operation timeout\")]\n    Timeout,\n}\n\ntype Result<T> = std::result::Result<T, HelloError>;\n\nimpl HelloWorld {\n    pub fn new(name: impl Into<String>) -> Self {\n        Self {\n            name: name.into(),\n            options: HashMap::new(),\n            created_at: chrono::Utc::now(),\n        }\n    }\n\n    // Greets multiple people asynchronously with configurable delay\n    pub async fn greet<T: AsRef<str>>(&self, names: &[T]) -> Result<()> {\n        for name in names {\n            time::sleep(Duration::from_millis(100)).await;\n            println!(\"Hello, {}!\", name.as_ref());\n        }\n        Ok(())\n    }\n\n    fn generate_report(&self) -> String {\n        format!(\n            \"HelloWorld Report\\n================\\nName: {}\\nCreated: {}\\nOptions: {:?}\",\n            self.name, self.created_at, self.options\n        )\n    }\n}\n\ntrait Configurable {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>);\n    fn is_configured(&self) -> bool;\n}\n\nimpl Configurable for HelloWorld {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>) {\n        self.options.extend(options);\n    }\n\n    fn is_configured(&self) -> bool {\n        !self.options.is_empty()\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let mut greeter = HelloWorld::new(\"Rust\");\n\n    let mut config = HashMap::new();\n    config.insert(\"timeout\".to_string(), serde_json::json!(5000));\n    config.insert(\"retries\".to_string(), serde_json::json!(3));\n\n    greeter.configure(config);\n\n    match greeter.greet(&[\"Alice\", \"Bob\"]).await {\n        Ok(_) => println!(\"Greetings sent successfully\"),\n        Err(e) => eprintln!(\"Error: {}\", e),\n    }\n\n    Ok(())\n}\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::time::Duration;\nuse tokio::time;\n\n// Version number of the HelloWorld struct\nconst VERSION: &str = \"1.0.0\";\n\n/// HelloWorld struct provides greeting functionality with configuration options\n///\n/// # Features\n/// - Async greetings with customizable names\n/// - Configuration management via HashMap\n/// - Report generation\n/// - Error handling with custom error types\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct HelloWorld {\n    name: String,\n    #[serde(skip)]\n    options: HashMap<String, serde_json::Value>,\n    created_at: chrono::DateTime<chrono::Utc>,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum HelloError {\n    #[error(\"Invalid name: {0}\")]\n    InvalidName(String),\n    #[error(\"Operation timeout\")]\n    Timeout,\n}\n\ntype Result<T> = std::result::Result<T, HelloError>;\n\nimpl HelloWorld {\n    pub fn new(name: impl Into<String>) -> Self {\n        Self {\n            name: name.into(),\n            options: HashMap::new(),\n            created_at: chrono::Utc::now(),\n        }\n    }\n\n    // Greets multiple people asynchronously with configurable delay\n    pub async fn greet<T: AsRef<str>>(&self, names: &[T]) -> Result<()> {\n        for name in names {\n            time::sleep(Duration::from_millis(100)).await;\n            println!(\"Hello, {}!\", name.as_ref());\n        }\n        Ok(())\n    }\n\n    fn generate_report(&self) -> String {\n        format!(\n            \"HelloWorld Report\\n================\\nName: {}\\nCreated: {}\\nOptions: {:?}\",\n            self.name, self.created_at, self.options\n        )\n    }\n}\n\ntrait Configurable {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>);\n    fn is_configured(&self) -> bool;\n}\n\nimpl Configurable for HelloWorld {\n    fn configure(&mut self, options: HashMap<String, serde_json::Value>) {\n        self.options.extend(options);\n    }\n\n    fn is_configured(&self) -> bool {\n        !self.options.is_empty()\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let mut greeter = HelloWorld::new(\"Rust\");\n\n    let mut config = HashMap::new();\n    config.insert(\"timeout\".to_string(), serde_json::json!(5000));\n    config.insert(\"retries\".to_string(), serde_json::json!(3));\n\n    greeter.configure(config);\n\n    match greeter.greet(&[\"Alice\", \"Bob\"]).await {\n        Ok(_) => println!(\"Greetings sent successfully\"),\n        Err(e) => eprintln!(\"Error: {}\", e),\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.sql",
    "content": "SELECT *\nFROM users\nWHERE email ilike '%test%'\n  AND deleted_at IS NOT NULL\nLIMIT 1;\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.svelte",
    "content": "<script lang=\"ts\">\n\t// Keep the following line to suppress type checking errors in this example\n\t// @ts-nocheck\n\timport { fade, slide } from 'svelte/transition';\n\n\tlet state = $state({\n\t\tcount: 0,\n\t\tname: 'World',\n\t\titems: ['Apple', 'Banana', 'Cherry'],\n\t\tnewItem: ''\n\t});\n\n\t// Those lines raise a warning in the IDE, uncomment in the editor example to see them highlighted\n\t// let summary = $derived(\n\t// \t`Hello ${state.name}! Count: ${state.count}, Items: ${state.items.length}`\n\t// );\n\t// $effect(() => {\n\t// \tconsole.log(`State updated:`, state);\n\t// });\n\n\tfunction increment() {\n\t\tstate.count++;\n\t}\n\n\tfunction decrement() {\n\t\tstate.count--;\n\t}\n\n\tfunction reset() {\n\t\tstate.count = 0;\n\t\tstate.name = 'World';\n\t}\n\n\tfunction addItem() {\n\t\tif (state.newItem.trim()) {\n\t\t\tstate.items.push(state.newItem.trim());\n\t\t\tstate.newItem = '';\n\t\t}\n\t}\n\n\tfunction removeItem(index: number) {\n\t\tstate.items.splice(index, 1);\n\t}\n\n\tasync function asyncIncrement() {\n\t\tawait new Promise((resolve) => setTimeout(resolve, 500));\n\t\tstate.count++;\n\t}\n</script>\n\n<main class=\"p-6 max-w-2xl mx-auto\">\n\t<h1 class=\"text-3xl font-bold mb-6\">Svelte 5 Features</h1>\n\n\t<section class=\"mb-6 p-4 border rounded\">\n\t\t<h2 class=\"text-xl font-semibold mb-3\">Counter</h2>\n\t\t<p class=\"mb-4\">Count: <strong>{state.count}</strong></p>\n\n\t\t<div class=\"flex gap-2\">\n\t\t\t<button onclick={increment} class=\"px-4 py-2 bg-blue-500 text-white rounded\">\n\t\t\t\tIncrement\n\t\t\t</button>\n\t\t\t<button onclick={decrement} class=\"px-4 py-2 bg-red-500 text-white rounded\">\n\t\t\t\tDecrement\n\t\t\t</button>\n\t\t\t<button onclick={asyncIncrement} class=\"px-4 py-2 bg-green-500 text-white rounded\">\n\t\t\t\tAsync +1\n\t\t\t</button>\n\t\t\t<button onclick={reset} class=\"px-4 py-2 bg-gray-500 text-white rounded\">Reset</button>\n\t\t</div>\n\t</section>\n\n\t<section class=\"mb-6 p-4 border rounded\">\n\t\t<h2 class=\"text-xl font-semibold mb-3\">Input Binding</h2>\n\t\t<input\n\t\t\ttype=\"text\"\n\t\t\tbind:value={state.name}\n\t\t\tplaceholder=\"Enter your name\"\n\t\t\tclass=\"px-3 py-2 border rounded w-full\"\n\t\t/>\n\t</section>\n\n\t<section class=\"mb-6 p-4 border rounded\">\n\t\t<h2 class=\"text-xl font-semibold mb-3\">List Management</h2>\n\n\t\t<div class=\"flex gap-2 mb-4\">\n\t\t\t<input\n\t\t\t\ttype=\"text\"\n\t\t\t\tbind:value={state.newItem}\n\t\t\t\tplaceholder=\"Add item\"\n\t\t\t\tclass=\"px-3 py-2 border rounded grow\"\n\t\t\t\tonkeydown={(e) => e.key === 'Enter' && addItem()}\n\t\t\t/>\n\t\t\t<button onclick={addItem} class=\"px-4 py-2 bg-blue-500 text-white rounded\">Add</button>\n\t\t</div>\n\n\t\t{#if state.items.length === 0}\n\t\t\t<p class=\"text-gray-500\" transition:fade>No items yet. Add some!</p>\n\t\t{:else}\n\t\t\t<ul class=\"space-y-2\">\n\t\t\t\t{#each state.items as item, i (item)}\n\t\t\t\t\t<li\n\t\t\t\t\t\tclass=\"flex justify-between items-center p-2 bg-gray-100 rounded\"\n\t\t\t\t\t\ttransition:slide\n\t\t\t\t\t>\n\t\t\t\t\t\t<span>{item}</span>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tonclick={() => removeItem(i)}\n\t\t\t\t\t\t\tclass=\"px-2 py-1 bg-red-500 text-white rounded text-sm\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tRemove\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</li>\n\t\t\t\t{/each}\n\t\t\t</ul>\n\t\t{/if}\n\t</section>\n</main>\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.ts",
    "content": "import fs, { readFile } from \"fs\";\n\nconst VERSION = \"1.0.0\";\n\nclass HelloWorld {\n  private name: string;\n  private createdAt: Date;\n  private options: Record<string, any>;\n\n  constructor(name: string) {\n    this.name = name;\n    this.createdAt = new Date();\n    this.options = {};\n  }\n\n  greet(names: string[]): void {\n    for (const name of names) {\n      console.log(`Hello, ${name}!`);\n    }\n  }\n\n  configure(cfg: { timeout: number; retries: number; debug: boolean }): void {\n    this.options[\"timeout\"] = cfg.timeout;\n    this.options[\"retries\"] = cfg.retries;\n    this.options[\"debug\"] = cfg.debug;\n  }\n\n  generateReport(): string {\n    return `\n        HelloWorld Report\n        =================\n        Name: ${this.name}\n        Created: ${this.createdAt.toISOString()}\n        Options: ${JSON.stringify(this.options, null, 2)}\n    `;\n  }\n}\n\nconst timeout = 5000;\n\nfunction main() {\n  const greeter = new HelloWorld(\"TypeScript\");\n  greeter.configure({\n    timeout: timeout,\n    retries: 3,\n    debug: true,\n  });\n\n  greeter.greet([\"Alice\", \"Bob\"]);\n  console.log(greeter.generateReport());\n}\n"
  },
  {
    "path": "crates/story/examples/fixtures/test.zig",
    "content": "const std = @import(\"std\");\nconst json = std.json;\nconst time = std.time;\nconst HashMap = std.HashMap;\n\npub const VERSION = \"1.0.0\";\n\npub const HelloError = error{\n    InvalidName,\n    Timeout,\n};\n\npub const HelloWorld = struct {\n    name: []const u8,\n    options: HashMap([]const u8, json.Value),\n    created_at: i64,\n\n    pub fn init(allocator: *std.mem.Allocator, name: []const u8) !HelloWorld {\n        return HelloWorld{\n            .name = name,\n            .options = HashMap([]const u8, json.Value).init(allocator),\n            .created_at = time.timestamp(),\n        };\n    }\n\n    pub fn deinit(self: *HelloWorld) void {\n        self.options.deinit();\n    }\n\n    pub fn greet(self: *const HelloWorld, names: []const []const u8) !void {\n        for (names) |name| {\n            time.sleep(100 * time.millisecond);\n            std.debug.print(\"Hello, {s}!\\n\", .{name});\n        }\n    }\n\n    pub fn configure(self: *HelloWorld, options: HashMap([]const u8, json.Value)) void {\n        var it = options.iterator();\n        while (it.next()) |entry| {\n            self.options.put(entry.key, entry.value) catch {};\n        }\n    }\n\n    pub fn generateReport(self: *const HelloWorld) ![]const u8 {\n        var report = std.ArrayList(u8).init(std.heap.page_allocator);\n        defer report.deinit();\n\n        try report.writer().print(\n            \\\\HelloWorld Report\n            \\\\================\n            \\\\Name: {s}\n            \\\\Created: {}\n            \\\\Options: {}\n            \\\\\n        , .{\n            self.name,\n            self.created_at,\n            self.options,\n        });\n\n        return report.toOwnedSlice();\n    }\n};\n\npub fn main() !void {\n    var gpa = std.heap.GeneralPurposeAllocator(.{}){};\n    defer _ = gpa.deinit();\n    const allocator = &gpa.allocator;\n\n    var greeter = try HelloWorld.init(allocator, \"Zig\");\n    defer greeter.deinit();\n\n    var config = HashMap([]const u8, json.Value).init(allocator);\n    try config.put(\"timeout\", json.Value{ .Integer = 5000 });\n    try config.put(\"retries\", json.Value{ .Integer = 3 });\n    \n    greeter.configure(config);\n\n    const names = [_][]const u8{ \"Alice\", \"Bob\" };\n    try greeter.greet(&names);\n\n    const report = try greeter.generateReport();\n    std.debug.print(\"{s}\\n\", .{report});\n}\n"
  },
  {
    "path": "crates/story/examples/html.rs",
    "content": "use gpui::*;\nuse gpui_component::{\n    ActiveTheme as _,\n    highlighter::Language,\n    input::{Input, InputState, TabSize},\n    resizable::h_resizable,\n    text::html,\n};\nuse gpui_component_assets::Assets;\n\npub struct Example {\n    input_state: Entity<InputState>,\n    _subscribe: Subscription,\n}\n\nconst EXAMPLE: &str = include_str!(\"./fixtures/test.html\");\n\nimpl Example {\n    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let input_state = cx.new(|cx| {\n            InputState::new(window, cx)\n                .code_editor(Language::Html)\n                .tab_size(TabSize {\n                    tab_size: 4,\n                    hard_tabs: false,\n                })\n                .default_value(EXAMPLE)\n                .placeholder(\"Enter your HTML here...\")\n        });\n\n        let _subscribe = cx.subscribe(\n            &input_state,\n            |_, _, _: &gpui_component::input::InputEvent, cx| {\n                cx.notify();\n            },\n        );\n\n        Self {\n            input_state,\n            _subscribe,\n        }\n    }\n\n    fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\n\nimpl Render for Example {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        h_resizable(\"container\")\n            .child(\n                div()\n                    .id(\"source\")\n                    .size_full()\n                    .font_family(cx.theme().mono_font_family.clone())\n                    .text_size(cx.theme().mono_font_size)\n                    .child(\n                        Input::new(&self.input_state)\n                            .h_full()\n                            .appearance(false)\n                            .focus_bordered(false),\n                    )\n                    .into_any(),\n            )\n            .child(\n                html(self.input_state.read(cx).value().clone())\n                    .p_5()\n                    .scrollable(true)\n                    .selectable(true)\n                    .into_any(),\n            )\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application().with_assets(Assets);\n\n    app.run(move |cx| {\n        gpui_component_story::init(cx);\n        cx.activate(true);\n\n        gpui_component_story::create_new_window(\"HTML Render (native)\", Example::view, cx);\n    });\n}\n"
  },
  {
    "path": "crates/story/examples/large-text.rs",
    "content": "use gpui::*;\nuse gpui_component::{\n    ActiveTheme, Selectable, Sizable, WindowExt,\n    button::{Button, ButtonVariants as _},\n    h_flex,\n    input::{self, Input, InputEvent, InputState, TabSize},\n    v_flex,\n};\nuse gpui_component_assets::Assets;\n\npub struct Example {\n    editor: Entity<InputState>,\n    go_to_line_state: Entity<InputState>,\n    soft_wrap: bool,\n    _subscribes: Vec<Subscription>,\n}\n\nimpl Example {\n    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        // 10K lines\n        let text = \"这是一个中文演示段落，用于展示更多的 [Markdown GFM] 内容。您可以在此尝试使用使用**粗体**、*斜体*和`代码`等样式。これは日本語のデモ段落です。Markdown の多言語サポートを示すためのテキストが含まれています。例えば、、**ボールド**、_イタリック_、および`コード`のスタイルなどを試すことができます。\\n\".repeat(10000);\n\n        let editor = cx.new(|cx| {\n            InputState::new(window, cx)\n                .multi_line(true)\n                .tab_size(TabSize { tab_size: 4, hard_tabs: false })\n                .soft_wrap(true)\n                .placeholder(\"Enter your code here...\")\n                .default_value(text)\n        });\n        let go_to_line_state = cx.new(|cx| InputState::new(window, cx));\n\n        let _subscribes = vec![cx.subscribe(&editor, |_, _, _: &InputEvent, cx| {\n            cx.notify();\n        })];\n\n        Self { editor, go_to_line_state, soft_wrap: false, _subscribes }\n    }\n\n    fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn go_to_line(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {\n        let editor = self.editor.clone();\n        let input_state = self.go_to_line_state.clone();\n\n        window.open_dialog(cx, move |dialog, window, cx| {\n            input_state.update(cx, |state, cx| {\n                let position = editor.read(cx).cursor_position();\n                state.set_placeholder(\n                    format!(\"{}:{}\", position.line, position.character),\n                    window,\n                    cx,\n                );\n                state.focus(window, cx);\n            });\n\n            dialog.title(\"Go to line\").child(Input::new(&input_state)).on_ok({\n                let editor = editor.clone();\n                let input_state = input_state.clone();\n                move |_, window, cx| {\n                    let query = input_state.read(cx).value();\n                    let mut parts = query\n                        .split(':')\n                        .map(|s| s.trim().parse::<usize>().ok())\n                        .collect::<Vec<_>>()\n                        .into_iter();\n                    let Some(line) = parts.next().and_then(|l| l) else {\n                        return false;\n                    };\n                    let line = line.saturating_sub(1);\n                    let column = parts.next().and_then(|c| c).unwrap_or(1).saturating_sub(1);\n\n                    editor.update(cx, |state, cx| {\n                        state.set_cursor_position(\n                            input::Position::new(line as u32, column as u32),\n                            window,\n                            cx,\n                        );\n                    });\n\n                    true\n                }\n            })\n        });\n    }\n\n    fn toggle_soft_wrap(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {\n        self.soft_wrap = !self.soft_wrap;\n        self.editor.update(cx, |state, cx| {\n            state.set_soft_wrap(self.soft_wrap, window, cx);\n        });\n        cx.notify();\n    }\n}\n\nimpl Render for Example {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex().size_full().child(\n            v_flex()\n                .id(\"source\")\n                .w_full()\n                .flex_1()\n                .child(Input::new(&self.editor).bordered(false).h_full().focus_bordered(false))\n                .child(\n                    h_flex()\n                        .justify_between()\n                        .text_sm()\n                        .bg(cx.theme().secondary)\n                        .py_1p5()\n                        .px_4()\n                        .border_t_1()\n                        .border_color(cx.theme().border)\n                        .text_color(cx.theme().muted_foreground)\n                        .child(h_flex().gap_3().child({\n                            Button::new(\"soft-wrap\")\n                                .ghost()\n                                .xsmall()\n                                .label(\"Soft Wrap\")\n                                .selected(self.soft_wrap)\n                                .on_click(cx.listener(Self::toggle_soft_wrap))\n                        }))\n                        .child({\n                            let loc = self.editor.read(cx).cursor_position();\n                            let cursor = self.editor.read(cx).cursor();\n\n                            Button::new(\"line-column\")\n                                .ghost()\n                                .xsmall()\n                                .label(format!(\"{}:{} ({} c)\", loc.line, loc.character, cursor))\n                                .on_click(cx.listener(Self::go_to_line))\n                        }),\n                ),\n        )\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application().with_assets(Assets);\n\n    app.run(move |cx| {\n        gpui_component_story::init(cx);\n        cx.activate(true);\n\n        gpui_component_story::create_new_window(\"Large Text Editor\", Example::view, cx);\n    });\n}\n"
  },
  {
    "path": "crates/story/examples/markdown.rs",
    "content": "use gpui::{prelude::FluentBuilder as _, *};\nuse gpui_component::{\n    ActiveTheme as _, IconName, Sizable as _,\n    button::{Button, ButtonVariants as _},\n    clipboard::Clipboard,\n    h_flex,\n    highlighter::Language,\n    input::{Input, InputEvent, InputState, TabSize},\n    resizable::{h_resizable, resizable_panel},\n    text::markdown,\n};\nuse gpui_component_assets::Assets;\nuse gpui_component_story::Open;\n\npub struct Example {\n    input_state: Entity<InputState>,\n    _subscriptions: Vec<Subscription>,\n}\n\nconst EXAMPLE: &str = include_str!(\"./fixtures/test.md\");\n\nimpl Example {\n    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let input_state = cx.new(|cx| {\n            InputState::new(window, cx)\n                .code_editor(Language::Markdown)\n                .line_number(true)\n                .tab_size(TabSize {\n                    tab_size: 2,\n                    ..Default::default()\n                })\n                .searchable(true)\n                .placeholder(\"Enter your Markdown here...\")\n                .default_value(EXAMPLE)\n        });\n\n        let _subscriptions = vec![cx.subscribe(&input_state, |_, _, _: &InputEvent, _| {})];\n\n        Self {\n            input_state,\n            _subscriptions,\n        }\n    }\n\n    fn on_action_open(&mut self, _: &Open, window: &mut Window, cx: &mut Context<Self>) {\n        let path = cx.prompt_for_paths(PathPromptOptions {\n            files: true,\n            directories: true,\n            multiple: false,\n            prompt: Some(\"Select a Markdown file\".into()),\n        });\n\n        let input_state = self.input_state.clone();\n        cx.spawn_in(window, async move |_, window| {\n            let path = path.await.ok()?.ok()??.iter().next()?.clone();\n\n            let content = std::fs::read_to_string(&path).ok()?;\n\n            window\n                .update(|window, cx| {\n                    _ = input_state.update(cx, |this, cx| {\n                        this.set_value(content, window, cx);\n                    });\n                })\n                .ok();\n\n            Some(())\n        })\n        .detach();\n    }\n\n    fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\n\nimpl Render for Example {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .id(\"editor\")\n            .size_full()\n            .on_action(cx.listener(Self::on_action_open))\n            .child(\n                h_resizable(\"container\")\n                    .child(\n                        resizable_panel().child(\n                            div()\n                                .id(\"source\")\n                                .size_full()\n                                .font_family(cx.theme().mono_font_family.clone())\n                                .text_size(cx.theme().mono_font_size)\n                                .child(\n                                    Input::new(&self.input_state)\n                                        .h_full()\n                                        .p_0()\n                                        .border_0()\n                                        .focus_bordered(false),\n                                ),\n                        ),\n                    )\n                    .child(\n                        resizable_panel().child(\n                            markdown(self.input_state.read(cx).value().clone())\n                                .code_block_actions(|code_block, _window, _cx| {\n                                    let code = code_block.code();\n                                    let lang = code_block.lang();\n\n                                    h_flex()\n                                        .gap_1()\n                                        .child(Clipboard::new(\"copy\").value(code.clone()))\n                                        .when_some(lang, |this, lang| {\n                                            // Only show run terminal button for certain languages\n                                            if lang.as_ref() == \"rust\" || lang.as_ref() == \"python\"\n                                            {\n                                                this.child(\n                                                    Button::new(\"run-terminal\")\n                                                        .icon(IconName::SquareTerminal)\n                                                        .ghost()\n                                                        .xsmall()\n                                                        .on_click(move |_, _, _cx| {\n                                                            println!(\n                                                                \"Running {} code: {}\",\n                                                                lang, code\n                                                            );\n                                                        }),\n                                                )\n                                            } else {\n                                                this\n                                            }\n                                        })\n                                })\n                                .flex_none()\n                                .p_5()\n                                .scrollable(true)\n                                .selectable(true),\n                        ),\n                    ),\n            )\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application().with_assets(Assets);\n\n    app.run(move |cx| {\n        gpui_component_story::init(cx);\n        cx.activate(true);\n\n        gpui_component_story::create_new_window(\"Markdown Editor\", Example::view, cx);\n    });\n}\n"
  },
  {
    "path": "crates/story/examples/stream_markdown.rs",
    "content": "use gpui::*;\nuse gpui_component::{\n    button::Button,\n    h_flex,\n    text::{TextView, TextViewState},\n    v_flex,\n};\nuse gpui_component_assets::Assets;\n\npub struct Example {\n    markdown_state: Entity<TextViewState>,\n    tx: smol::channel::Sender<String>,\n    scroll_handle: ScrollHandle,\n    _task: Task<()>,\n    _update_task: Task<()>,\n}\n\nconst EXAMPLE: &str = include_str!(\"./fixtures/test.md\");\n\nimpl Example {\n    pub fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        let markdown_state =\n            cx.new(|cx| TextViewState::markdown(\"# Streaming Markdown Parse\\n\\n\", cx));\n        let scroll_handle = ScrollHandle::new();\n\n        let (tx, rx) = smol::channel::unbounded::<String>();\n        let _task = cx.spawn({\n            let scroll_handle = scroll_handle.clone();\n            let weak_state = markdown_state.downgrade();\n            async move |_, cx| {\n                while let Ok(chunk) = rx.recv().await {\n                    _ = weak_state.update(cx, |state, cx| {\n                        // Push the new chunk to the markdown state,\n                        // it will reparse and re-render automatically.\n                        state.push_str(&chunk, cx);\n                        scroll_handle.scroll_to_bottom();\n                    });\n                }\n            }\n        });\n\n        Self {\n            markdown_state,\n            scroll_handle,\n            tx,\n            _task,\n            _update_task: Task::ready(()),\n        }\n    }\n\n    fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    /// Simulate streaming by updating markdown state in chunks\n    /// 50ms for a iteration, every time adding about 5 - 20 characters\n    /// This is just for demonstration; in a real app, you'd stream from a source.\n    fn replay(&mut self, _window: &mut Window, cx: &mut Context<Self>) {\n        let tx = self.tx.clone();\n        let mut current = 0;\n        self.markdown_state.update(cx, |state, cx| {\n            state.set_text(\"\", cx);\n        });\n\n        self._update_task = cx.background_executor().spawn(async move {\n            let chars: Vec<char> = EXAMPLE.chars().collect();\n            while current < chars.len() {\n                let chunk_size = (5 + rand::random::<usize>() % 15).min(chars.len() - current);\n                let chunk: String = chars[current..current + chunk_size].iter().collect();\n                _ = tx.try_send(chunk);\n                current += chunk_size;\n                std::thread::sleep(std::time::Duration::from_millis(50));\n            }\n        });\n    }\n}\n\nimpl Render for Example {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .id(\"example\")\n            .size_full()\n            .p_4()\n            .gap_4()\n            .child(\n                h_flex()\n                    .w_full()\n                    .child(\n                        Button::new(\"replay\")\n                            .outline()\n                            .label(\"Replay\")\n                            .on_click(cx.listener(move |this, _, window, cx| {\n                                this.replay(window, cx);\n                            })),\n                    ),\n            )\n            .child(\n                div()\n                    .id(\"contents\")\n                    .flex_1()\n                    .w_full()\n                    .track_scroll(&self.scroll_handle)\n                    .overflow_y_scroll()\n                    .size_full()\n                    .child(TextView::new(&self.markdown_state).selectable(true)),\n            )\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application().with_assets(Assets);\n\n    app.run(move |cx| {\n        gpui_component_story::init(cx);\n        cx.activate(true);\n\n        gpui_component_story::create_new_window_with_size(\n            \"Stream Markdown\",\n            Some(size(px(600.), px(800.))),\n            Example::view,\n            cx,\n        );\n    });\n}\n"
  },
  {
    "path": "crates/story/examples/tiles.rs",
    "content": "use anyhow::{Context as _, Result};\nuse gpui::*;\nuse gpui_component::{\n    ActiveTheme, Root, Sizable, TitleBar,\n    dock::{\n        DockArea, DockAreaState, DockEvent, DockItem, Panel, PanelEvent, PanelInfo, PanelRegistry,\n        PanelState, PanelView, register_panel,\n    },\n    input::{Input, InputState},\n    scroll::ScrollbarShow,\n};\nuse gpui_component_assets::Assets;\nuse gpui_component_story::{ButtonStory, IconStory, StoryContainer};\nuse serde::{Deserialize, Serialize};\nuse std::{sync::Arc, time::Duration};\n\nactions!(tiles_story, [Quit]);\n\nconst TILES_DOCK_AREA: DockAreaTab = DockAreaTab {\n    id: \"story-tiles\",\n    version: 1,\n};\n\n/// A specification for a container panel for wrapping other panels to add some common functionality.\n///\n/// For example:\n///\n/// - Add a search bar to all panels.\nstruct ContainerPanel {\n    panel: Arc<dyn PanelView>,\n    search_state: Entity<InputState>,\n}\n\n#[derive(Clone, Serialize, Deserialize)]\nstruct ContainerPanelState {\n    /// The state of the child panel.\n    child: PanelState,\n}\n\nimpl ContainerPanelState {\n    fn new(child: PanelState) -> Self {\n        Self { child }\n    }\n\n    fn to_value(&self) -> serde_json::Value {\n        serde_json::to_value(self).unwrap()\n    }\n\n    fn from_value(value: serde_json::Value) -> Result<Self> {\n        serde_json::from_value(value).context(\"failed to deserialize ContainerPanelState\")\n    }\n}\n\nimpl ContainerPanel {\n    fn init(cx: &mut App) {\n        register_panel(\n            cx,\n            \"ContainerPanel\",\n            |dock_area, _, info, window, cx| match info {\n                PanelInfo::Panel(panel_info) => {\n                    let container_state =\n                        ContainerPanelState::from_value(panel_info.clone()).unwrap();\n                    let child_state = container_state.child;\n                    let view = PanelRegistry::build_panel(\n                        &child_state.panel_name,\n                        dock_area,\n                        &child_state,\n                        &child_state.info,\n                        window,\n                        cx,\n                    );\n\n                    Box::new(ContainerPanel::new(view.into(), window, cx))\n                }\n                _ => unreachable!(),\n            },\n        );\n    }\n\n    fn new(panel: Arc<dyn PanelView>, window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| {\n            let search_state = cx.new(|cx| InputState::new(window, cx).placeholder(\"Search...\"));\n\n            Self {\n                panel,\n                search_state,\n            }\n        })\n    }\n}\n\nimpl Panel for ContainerPanel {\n    fn panel_name(&self) -> &'static str {\n        \"ContainerPanel\"\n    }\n\n    fn title(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        self.panel.title(window, cx)\n    }\n\n    fn title_suffix(&mut self, _: &mut Window, cx: &mut Context<Self>) -> Option<impl IntoElement> {\n        Some(\n            div()\n                .w_24()\n                .h_6()\n                .px_0p5()\n                .rounded(cx.theme().radius_lg)\n                .border_1()\n                .border_color(cx.theme().input)\n                .child(Input::new(&self.search_state).xsmall().appearance(false))\n                .into_any_element(),\n        )\n    }\n\n    fn dump(&self, cx: &App) -> PanelState {\n        let mut state = PanelState::new(self);\n        let panel_state = self.panel.dump(cx);\n        let json_value = ContainerPanelState::new(panel_state).to_value();\n        state.info = PanelInfo::panel(json_value);\n        state\n    }\n}\n\nimpl EventEmitter<PanelEvent> for ContainerPanel {}\nimpl Focusable for ContainerPanel {\n    fn focus_handle(&self, cx: &App) -> FocusHandle {\n        self.panel.focus_handle(cx)\n    }\n}\n\nimpl Render for ContainerPanel {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        self.panel.view().clone()\n    }\n}\n\nactions!(workspace, [Open, CloseWindow]);\n\npub fn init(cx: &mut App) {\n    cx.on_action(|_action: &Open, _cx: &mut App| {});\n\n    gpui_component::init(cx);\n    gpui_component_story::init(cx);\n}\n\npub struct StoryTiles {\n    dock_area: Entity<DockArea>,\n    last_layout_state: Option<DockAreaState>,\n    _save_layout_task: Option<Task<()>>,\n}\n\nstruct DockAreaTab {\n    id: &'static str,\n    version: usize,\n}\n\nimpl StoryTiles {\n    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let dock_area = cx.new(|cx| {\n            DockArea::new(\n                TILES_DOCK_AREA.id,\n                Some(TILES_DOCK_AREA.version),\n                window,\n                cx,\n            )\n        });\n        let weak_dock_area = dock_area.downgrade();\n\n        match Self::load_tiles(dock_area.clone(), window, cx) {\n            Ok(_) => {\n                println!(\"load tiles success\");\n            }\n            Err(err) => {\n                eprintln!(\"load tiles error: {:?}\", err);\n                Self::reset_default_layout(weak_dock_area, window, cx);\n            }\n        };\n\n        cx.subscribe_in(\n            &dock_area,\n            window,\n            |this, dock_area, ev: &DockEvent, window, cx| match ev {\n                DockEvent::LayoutChanged => this.save_layout(dock_area, window, cx),\n                DockEvent::DragDrop(item) => {\n                    println!(\"drag drop: {:?}\", item);\n                }\n            },\n        )\n        .detach();\n\n        cx.on_app_quit({\n            let dock_area = dock_area.clone();\n            move |_, cx| {\n                let state = dock_area.read(cx).dump(cx);\n                cx.background_executor().spawn(async move {\n                    // Save layout before quitting\n                    Self::save_tiles(&state).unwrap();\n                })\n            }\n        })\n        .detach();\n\n        Self {\n            dock_area,\n            last_layout_state: None,\n            _save_layout_task: None,\n        }\n    }\n\n    fn save_layout(\n        &mut self,\n        dock_area: &Entity<DockArea>,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let dock_area = dock_area.clone();\n        self._save_layout_task = Some(cx.spawn(async move |this, cx| {\n            cx.background_executor()\n                .timer(Duration::from_secs(10))\n                .await;\n\n            let _ = cx.update(|cx| {\n                let dock_area = dock_area.read(cx);\n                let state = dock_area.dump(cx);\n\n                let last_layout_state = this.upgrade().unwrap().read(cx).last_layout_state.clone();\n                if Some(&state) == last_layout_state.as_ref() {\n                    return;\n                }\n\n                Self::save_tiles(&state).unwrap();\n                let _ = this.update(cx, |this, _| {\n                    this.last_layout_state = Some(state);\n                });\n            });\n        }));\n    }\n\n    fn save_tiles(state: &DockAreaState) -> Result<()> {\n        println!(\"Save tiles...\");\n        let json = serde_json::to_string_pretty(state)?;\n        std::fs::write(\"target/tiles.json\", json)?;\n        Ok(())\n    }\n\n    fn set_scrollbar_show(dock_area: &mut DockArea, cx: &mut App) {\n        match dock_area.center() {\n            DockItem::Tiles { view, .. } => {\n                view.update(cx, |this, cx| {\n                    this.set_scrollbar_show(Some(ScrollbarShow::Always), cx);\n                });\n            }\n            _ => {}\n        }\n    }\n\n    fn load_tiles(\n        dock_area: Entity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Result<()> {\n        let fname = \"target/tiles.json\";\n        let json = std::fs::read_to_string(fname)?;\n        let state = serde_json::from_str::<DockAreaState>(&json)?;\n\n        // Check if the saved layout version is different from the current version\n        // Notify the user and ask if they want to reset the layout to default.\n        if state.version != Some(TILES_DOCK_AREA.version) {\n            let answer = window.prompt(\n                PromptLevel::Info,\n                \"The default tiles layout has been updated.\\n\\\n                Do you want to reset the layout to default?\",\n                None,\n                &[\"Yes\", \"No\"],\n                cx,\n            );\n\n            let weak_dock_area = dock_area.downgrade();\n            cx.spawn_in(window, async move |this, window| {\n                if answer.await == Ok(0) {\n                    _ = this.update_in(window, |_, window, cx| {\n                        Self::reset_default_layout(weak_dock_area, window, cx);\n                    });\n                }\n            })\n            .detach();\n        }\n\n        dock_area.update(cx, |dock_area, cx| {\n            dock_area.load(state, window, cx).context(\"load layout\")?;\n            Self::set_scrollbar_show(dock_area, cx);\n            Ok::<(), anyhow::Error>(())\n        })\n    }\n\n    fn reset_default_layout(\n        dock_area: WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let dock_item = Self::init_default_layout(&dock_area, window, cx);\n        _ = dock_area.update(cx, |dock_area, cx| {\n            dock_area.set_version(TILES_DOCK_AREA.version, window, cx);\n            dock_area.set_center(dock_item, window, cx);\n\n            Self::set_scrollbar_show(dock_area, cx);\n            Self::save_tiles(&dock_area.dump(cx)).unwrap();\n        });\n    }\n\n    fn init_default_layout(\n        dock_area: &WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> DockItem {\n        const PANELS: usize = 4;\n        let panels = (0..PANELS)\n            .map(|i| {\n                let story = if i % 2 == 0 {\n                    Arc::new(StoryContainer::panel::<ButtonStory>(window, cx))\n                } else {\n                    Arc::new(StoryContainer::panel::<IconStory>(window, cx))\n                };\n                DockItem::tab(\n                    ContainerPanel::new(story, window, cx),\n                    dock_area,\n                    window,\n                    cx,\n                )\n            })\n            .collect::<Vec<_>>();\n\n        // Panel size: 380x280, Gap: 20px, Starting position: (20, 20)\n        let panel_width = px(380.);\n        let panel_height = px(280.);\n        let gap = px(20.);\n        let start_x = px(20.);\n        let start_y = px(20.);\n        let cols = 4;\n\n        let bounds = (0..PANELS)\n            .map(|i| {\n                let row = i / cols;\n                let col = i % cols;\n                let x = start_x + (panel_width + gap) * col as f32;\n                let y = start_y + (panel_height + gap) * row as f32;\n                Bounds::new(point(x, y), size(panel_width, panel_height))\n            })\n            .collect::<Vec<_>>();\n\n        DockItem::tiles(panels, bounds, dock_area, window, cx)\n    }\n\n    pub fn new_local(cx: &mut App) -> Task<anyhow::Result<WindowHandle<Root>>> {\n        let mut window_size = size(px(1600.0), px(1200.0));\n        if let Some(display) = cx.primary_display() {\n            let display_size = display.bounds().size;\n            window_size.width = window_size.width.min(display_size.width * 0.85);\n            window_size.height = window_size.height.min(display_size.height * 0.85);\n        }\n        let window_bounds = Bounds::centered(None, window_size, cx);\n\n        cx.spawn(async move |cx| {\n            let options = WindowOptions {\n                window_bounds: Some(WindowBounds::Windowed(window_bounds)),\n                titlebar: Some(TitlebarOptions {\n                    title: None,\n                    appears_transparent: true,\n                    traffic_light_position: Some(point(px(9.0), px(9.0))),\n                }),\n                window_min_size: Some(gpui::Size {\n                    width: px(640.),\n                    height: px(480.),\n                }),\n                kind: WindowKind::Normal,\n                #[cfg(target_os = \"linux\")]\n                window_background: gpui::WindowBackgroundAppearance::Transparent,\n                #[cfg(target_os = \"linux\")]\n                window_decorations: Some(gpui::WindowDecorations::Client),\n                ..Default::default()\n            };\n\n            let window = cx.open_window(options, |window, cx| {\n                let tiles_view = cx.new(|cx| Self::new(window, cx));\n                cx.new(|cx| Root::new(tiles_view, window, cx))\n            })?;\n\n            window\n                .update(cx, |_, window, _| {\n                    window.activate_window();\n                    window.set_window_title(\"Story Tiles\");\n                })\n                .expect(\"failed to update window\");\n\n            Ok(window)\n        })\n    }\n}\n\npub fn open_new(\n    cx: &mut App,\n    init: impl FnOnce(&mut Root, &mut Window, &mut Context<Root>) + 'static + Send,\n) -> Task<()> {\n    let task: Task<std::result::Result<WindowHandle<Root>, anyhow::Error>> =\n        StoryTiles::new_local(cx);\n    cx.spawn(async move |cx| {\n        if let Some(root) = task.await.ok() {\n            root.update(cx, |workspace, window, cx| init(workspace, window, cx))\n                .expect(\"failed to init workspace\");\n        }\n    })\n}\n\nimpl Render for StoryTiles {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let sheet_layer = Root::render_sheet_layer(window, cx);\n        let dialog_layer = Root::render_dialog_layer(window, cx);\n        let notification_layer = Root::render_notification_layer(window, cx);\n\n        div()\n            .font_family(cx.theme().font_family.clone())\n            .relative()\n            .size_full()\n            .flex()\n            .flex_col()\n            .bg(cx.theme().background)\n            .text_color(cx.theme().foreground)\n            .child(TitleBar::new().child(div().flex().items_center().child(\"Story Tiles\")))\n            .child(self.dock_area.clone())\n            .children(sheet_layer)\n            .children(dialog_layer)\n            .children(notification_layer)\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application().with_assets(Assets);\n\n    app.run(move |cx| {\n        gpui_component::init(cx);\n        gpui_component_story::init(cx);\n        ContainerPanel::init(cx);\n\n        cx.on_action(quit);\n\n        cx.set_menus(vec![Menu {\n            name: \"GPUI App\".into(),\n            items: vec![MenuItem::action(\"Quit\", Quit)],\n        }]);\n        cx.activate(true);\n\n        open_new(cx, |_, _, _| {\n            // do something\n        })\n        .detach();\n    });\n}\n\nfn quit(_: &Quit, cx: &mut App) {\n    cx.quit();\n}\n"
  },
  {
    "path": "crates/story/src/app_menus.rs",
    "content": "use gpui::{App, Entity, Menu, MenuItem, SharedString};\nuse gpui_component::{\n    ActiveTheme as _, GlobalState, Theme, ThemeMode, ThemeRegistry, menu::AppMenuBar,\n};\n\nuse crate::{\n    About, Open, Quit, SelectLocale, ToggleSearch,\n    themes::{SwitchTheme, SwitchThemeMode},\n};\n\npub fn init(title: impl Into<SharedString>, cx: &mut App) -> Entity<AppMenuBar> {\n    let app_menu_bar = AppMenuBar::new(cx);\n    let title: SharedString = title.into();\n    update_app_menu(title.clone(), app_menu_bar.clone(), cx);\n\n    cx.on_action({\n        let title = title.clone();\n        let app_menu_bar = app_menu_bar.clone();\n        move |s: &SelectLocale, cx: &mut App| {\n            rust_i18n::set_locale(&s.0.as_str());\n            update_app_menu(title.clone(), app_menu_bar.clone(), cx);\n        }\n    });\n\n    // Observe theme changes to update the menu to refresh the checked state\n    cx.observe_global::<Theme>({\n        let title = title.clone();\n        let app_menu_bar = app_menu_bar.clone();\n        move |cx| {\n            update_app_menu(title.clone(), app_menu_bar.clone(), cx);\n        }\n    })\n    .detach();\n\n    app_menu_bar\n}\n\nfn update_app_menu(title: impl Into<SharedString>, app_menu_bar: Entity<AppMenuBar>, cx: &mut App) {\n    let title: SharedString = title.into();\n\n    cx.set_menus(build_menus(title.clone(), cx));\n    let menus = build_menus(title, cx)\n        .into_iter()\n        .map(|menu| menu.owned())\n        .collect();\n    GlobalState::global_mut(cx).set_app_menus(menus);\n\n    app_menu_bar.update(cx, |menu_bar, cx| {\n        menu_bar.reload(cx);\n    })\n}\n\nfn build_menus(title: impl Into<SharedString>, cx: &App) -> Vec<Menu> {\n    vec![\n        Menu {\n            name: title.into(),\n            items: vec![\n                MenuItem::action(\"About\", About),\n                MenuItem::Separator,\n                MenuItem::action(\"Open...\", Open),\n                MenuItem::Separator,\n                MenuItem::Submenu(Menu {\n                    name: \"Appearance\".into(),\n                    items: vec![\n                        MenuItem::action(\"Light\", SwitchThemeMode(ThemeMode::Light))\n                            .checked(!cx.theme().mode.is_dark()),\n                        MenuItem::action(\"Dark\", SwitchThemeMode(ThemeMode::Dark))\n                            .checked(cx.theme().mode.is_dark()),\n                    ],\n                }),\n                theme_menu(cx),\n                language_menu(cx),\n                MenuItem::Separator,\n                MenuItem::action(\"Quit\", Quit),\n            ],\n        },\n        Menu {\n            name: \"Edit\".into(),\n            items: vec![\n                MenuItem::action(\"Undo\", gpui_component::input::Undo),\n                MenuItem::action(\"Redo\", gpui_component::input::Redo),\n                MenuItem::separator(),\n                MenuItem::action(\"Cut\", gpui_component::input::Cut),\n                MenuItem::action(\"Copy\", gpui_component::input::Copy),\n                MenuItem::action(\"Paste\", gpui_component::input::Paste),\n                MenuItem::separator(),\n                MenuItem::action(\"Delete\", gpui_component::input::Delete),\n                MenuItem::action(\n                    \"Delete Previous Word\",\n                    gpui_component::input::DeleteToPreviousWordStart,\n                ),\n                MenuItem::action(\n                    \"Delete Next Word\",\n                    gpui_component::input::DeleteToNextWordEnd,\n                ),\n                MenuItem::separator(),\n                MenuItem::action(\"Find\", gpui_component::input::Search),\n                MenuItem::separator(),\n                MenuItem::action(\"Select All\", gpui_component::input::SelectAll),\n            ],\n        },\n        Menu {\n            name: \"Window\".into(),\n            items: vec![MenuItem::action(\"Toggle Search\", ToggleSearch)],\n        },\n        Menu {\n            name: \"Help\".into(),\n            items: vec![MenuItem::action(\"Open Website\", Open)],\n        },\n    ]\n}\n\nfn language_menu(_: &App) -> MenuItem {\n    let locale = rust_i18n::locale().to_string();\n    MenuItem::Submenu(Menu {\n        name: \"Language\".into(),\n        items: vec![\n            MenuItem::action(\"English\", SelectLocale(\"en\".into())).checked(locale == \"en\"),\n            MenuItem::action(\"简体中文\", SelectLocale(\"zh-CN\".into())).checked(locale == \"zh-CN\"),\n        ],\n    })\n}\n\nfn theme_menu(cx: &App) -> MenuItem {\n    let themes = ThemeRegistry::global(cx).sorted_themes();\n    let current_name = cx.theme().theme_name();\n    MenuItem::Submenu(Menu {\n        name: \"Theme\".into(),\n        items: themes\n            .iter()\n            .map(|theme| {\n                let checked = current_name == &theme.name;\n                MenuItem::action(theme.name.clone(), SwitchTheme(theme.name.clone()))\n                    .checked(checked)\n            })\n            .collect(),\n    })\n}\n"
  },
  {
    "path": "crates/story/src/embedded_themes.rs",
    "content": "#[cfg(target_family = \"wasm\")]\nuse std::collections::HashMap;\n\n#[cfg(target_family = \"wasm\")]\npub fn embedded_themes() -> HashMap<&'static str, &'static str> {\n    let mut themes = HashMap::new();\n\n    themes.insert(\"adventure\", include_str!(\"../../../themes/adventure.json\"));\n    themes.insert(\"alduin\", include_str!(\"../../../themes/alduin.json\"));\n    themes.insert(\"asciinema\", include_str!(\"../../../themes/asciinema.json\"));\n    themes.insert(\"ayu\", include_str!(\"../../../themes/ayu.json\"));\n    themes.insert(\n        \"catppuccin\",\n        include_str!(\"../../../themes/catppuccin.json\"),\n    );\n    themes.insert(\n        \"everforest\",\n        include_str!(\"../../../themes/everforest.json\"),\n    );\n    themes.insert(\n        \"fahrenheit\",\n        include_str!(\"../../../themes/fahrenheit.json\"),\n    );\n    themes.insert(\"flexoki\", include_str!(\"../../../themes/flexoki.json\"));\n    themes.insert(\"gruvbox\", include_str!(\"../../../themes/gruvbox.json\"));\n    themes.insert(\"harper\", include_str!(\"../../../themes/harper.json\"));\n    themes.insert(\"hybrid\", include_str!(\"../../../themes/hybrid.json\"));\n    themes.insert(\n        \"jellybeans\",\n        include_str!(\"../../../themes/jellybeans.json\"),\n    );\n    themes.insert(\"kibble\", include_str!(\"../../../themes/kibble.json\"));\n    themes.insert(\n        \"macos-classic\",\n        include_str!(\"../../../themes/macos-classic.json\"),\n    );\n    themes.insert(\"matrix\", include_str!(\"../../../themes/matrix.json\"));\n    themes.insert(\n        \"mellifluous\",\n        include_str!(\"../../../themes/mellifluous.json\"),\n    );\n    themes.insert(\"molokai\", include_str!(\"../../../themes/molokai.json\"));\n    themes.insert(\"solarized\", include_str!(\"../../../themes/solarized.json\"));\n    themes.insert(\"spaceduck\", include_str!(\"../../../themes/spaceduck.json\"));\n    themes.insert(\n        \"tokyonight\",\n        include_str!(\"../../../themes/tokyonight.json\"),\n    );\n    themes.insert(\"twilight\", include_str!(\"../../../themes/twilight.json\"));\n\n    themes\n}\n"
  },
  {
    "path": "crates/story/src/fixtures/counters.json",
    "content": "[\n  { \"symbol\": \"AAPL\", \"name\": \"Apple Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"MSFT\", \"name\": \"Microsoft Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"GOOGL\", \"name\": \"Alphabet Inc. Class A\", \"market\": \"US\" },\n  { \"symbol\": \"AMZN\", \"name\": \"Amazon.com Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"META\", \"name\": \"Meta Platforms Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"TSLA\", \"name\": \"Tesla Inc.\", \"market\": \"US\" },\n  {\n    \"symbol\": \"BRK.B\",\n    \"name\": \"Berkshire Hathaway Inc. Class B\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"NVDA\", \"name\": \"NVIDIA Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"JPM\", \"name\": \"JPMorgan Chase & Co.\", \"market\": \"US\" },\n  { \"symbol\": \"V\", \"name\": \"Visa Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"UNH\", \"name\": \"UnitedHealth Group Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"MA\", \"name\": \"Mastercard Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"HD\", \"name\": \"Home Depot Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"PG\", \"name\": \"Procter & Gamble Co.\", \"market\": \"US\" },\n  { \"symbol\": \"LLY\", \"name\": \"Eli Lilly and Co.\", \"market\": \"US\" },\n  { \"symbol\": \"BAC\", \"name\": \"Bank of America Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"XOM\", \"name\": \"Exxon Mobil Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"KO\", \"name\": \"Coca-Cola Co.\", \"market\": \"US\" },\n  { \"symbol\": \"MRK\", \"name\": \"Merck & Co. Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"PEP\", \"name\": \"PepsiCo Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ABBV\", \"name\": \"AbbVie Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"AVGO\", \"name\": \"Broadcom Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"COST\", \"name\": \"Costco Wholesale Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"WMT\", \"name\": \"Walmart Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"MCD\", \"name\": \"McDonald's Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"ADBE\", \"name\": \"Adobe Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"CRM\", \"name\": \"Salesforce Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"NFLX\", \"name\": \"Netflix Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"TMO\", \"name\": \"Thermo Fisher Scientific Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ACN\", \"name\": \"Accenture plc\", \"market\": \"US\" },\n  { \"symbol\": \"LIN\", \"name\": \"Linde plc\", \"market\": \"US\" },\n  { \"symbol\": \"TXN\", \"name\": \"Texas Instruments Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"QCOM\", \"name\": \"Qualcomm Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"NEE\", \"name\": \"NextEra Energy Inc.\", \"market\": \"US\" },\n  {\n    \"symbol\": \"PM\",\n    \"name\": \"Philip Morris International Inc.\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"HON\", \"name\": \"Honeywell International Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ORCL\", \"name\": \"Oracle Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"AMGN\", \"name\": \"Amgen Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"INTC\", \"name\": \"Intel Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"DHR\", \"name\": \"Danaher Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"LOW\", \"name\": \"Lowe's Companies Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"UPS\", \"name\": \"United Parcel Service Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SBUX\", \"name\": \"Starbucks Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"CVX\", \"name\": \"Chevron Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"GS\", \"name\": \"Goldman Sachs Group Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"MDT\", \"name\": \"Medtronic plc\", \"market\": \"US\" },\n  { \"symbol\": \"AXP\", \"name\": \"American Express Co.\", \"market\": \"US\" },\n  { \"symbol\": \"SPGI\", \"name\": \"S&P Global Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"BLK\", \"name\": \"BlackRock Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SYK\", \"name\": \"Stryker Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"ISRG\", \"name\": \"Intuitive Surgical Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DE\", \"name\": \"Deere & Co.\", \"market\": \"US\" },\n  { \"symbol\": \"PLD\", \"name\": \"Prologis Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SCHW\", \"name\": \"Charles Schwab Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"BKNG\", \"name\": \"Booking Holdings Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"CI\", \"name\": \"Cigna Group\", \"market\": \"US\" },\n  { \"symbol\": \"CB\", \"name\": \"Chubb Ltd.\", \"market\": \"US\" },\n  {\n    \"symbol\": \"MMC\",\n    \"name\": \"Marsh & McLennan Companies Inc.\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"ADP\", \"name\": \"Automatic Data Processing Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"LRCX\", \"name\": \"Lam Research Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"CME\", \"name\": \"CME Group Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"TGT\", \"name\": \"Target Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"DUK\", \"name\": \"Duke Energy Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"GILD\", \"name\": \"Gilead Sciences Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"AMAT\", \"name\": \"Applied Materials Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"FISV\", \"name\": \"Fiserv Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"MO\", \"name\": \"Altria Group Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"VRTX\", \"name\": \"Vertex Pharmaceuticals Inc.\", \"market\": \"US\" },\n  {\n    \"symbol\": \"REGN\",\n    \"name\": \"Regeneron Pharmaceuticals Inc.\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"PGR\", \"name\": \"Progressive Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"ELV\", \"name\": \"Elevance Health Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"EOG\", \"name\": \"EOG Resources Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"FDX\", \"name\": \"FedEx Corp.\", \"market\": \"US\" },\n  {\n    \"symbol\": \"APD\",\n    \"name\": \"Air Products and Chemicals Inc.\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"AON\", \"name\": \"Aon plc\", \"market\": \"US\" },\n  { \"symbol\": \"ITW\", \"name\": \"Illinois Tool Works Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"BSX\", \"name\": \"Boston Scientific Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"GM\", \"name\": \"General Motors Co.\", \"market\": \"US\" },\n  { \"symbol\": \"HCA\", \"name\": \"HCA Healthcare Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"GD\", \"name\": \"General Dynamics Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"ZTS\", \"name\": \"Zoetis Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SYY\", \"name\": \"Sysco Corp.\", \"market\": \"US\" },\n  {\n    \"symbol\": \"AIG\",\n    \"name\": \"American International Group Inc.\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"MCO\", \"name\": \"Moody's Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"MS\", \"name\": \"Morgan Stanley\", \"market\": \"US\" },\n  { \"symbol\": \"SLB\", \"name\": \"Schlumberger Ltd.\", \"market\": \"US\" },\n  { \"symbol\": \"SO\", \"name\": \"Southern Co.\", \"market\": \"US\" },\n  { \"symbol\": \"WM\", \"name\": \"Waste Management Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"D\", \"name\": \"Dominion Energy Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"PSA\", \"name\": \"Public Storage\", \"market\": \"US\" },\n  { \"symbol\": \"CNC\", \"name\": \"Centene Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"TRV\", \"name\": \"Travelers Companies Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"AFL\", \"name\": \"Aflac Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"VLO\", \"name\": \"Valero Energy Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"WELL\", \"name\": \"Welltower Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"F\", \"name\": \"Ford Motor Co.\", \"market\": \"US\" },\n  { \"symbol\": \"STZ\", \"name\": \"Constellation Brands Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"NOC\", \"name\": \"Northrop Grumman Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"KHC\", \"name\": \"Kraft Heinz Co.\", \"market\": \"US\" },\n  { \"symbol\": \"EXC\", \"name\": \"Exelon Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"CARR\", \"name\": \"Carrier Global Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"HES\", \"name\": \"Hess Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"WMB\", \"name\": \"Williams Companies Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"VTR\", \"name\": \"Ventas Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DOW\", \"name\": \"Dow Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"BKR\", \"name\": \"Baker Hughes Co.\", \"market\": \"US\" },\n  { \"symbol\": \"OKE\", \"name\": \"ONEOK Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"NUE\", \"name\": \"Nucor Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"CPB\", \"name\": \"Campbell Soup Co.\", \"market\": \"US\" },\n  { \"symbol\": \"CL\", \"name\": \"Colgate-Palmolive Co.\", \"market\": \"US\" },\n  { \"symbol\": \"EMR\", \"name\": \"Emerson Electric Co.\", \"market\": \"US\" },\n  { \"symbol\": \"ETN\", \"name\": \"Eaton Corp. plc\", \"market\": \"US\" },\n  { \"symbol\": \"ROK\", \"name\": \"Rockwell Automation Inc.\", \"market\": \"US\" },\n  {\n    \"symbol\": \"IFF\",\n    \"name\": \"International Flavors & Fragrances Inc.\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"XYL\", \"name\": \"Xylem Inc.\", \"market\": \"US\" },\n  {\n    \"symbol\": \"WAB\",\n    \"name\": \"Westinghouse Air Brake Technologies Corp.\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"TDG\", \"name\": \"TransDigm Group Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"MAS\", \"name\": \"Masco Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"AWK\", \"name\": \"American Water Works Co. Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"PNR\", \"name\": \"Pentair plc\", \"market\": \"US\" },\n  { \"symbol\": \"AOS\", \"name\": \"A. O. Smith Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"NDSN\", \"name\": \"Nordson Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"ALB\", \"name\": \"Albemarle Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"APTV\", \"name\": \"Aptiv PLC\", \"market\": \"US\" },\n  { \"symbol\": \"BWA\", \"name\": \"BorgWarner Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"CUM\", \"name\": \"Cummins Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DOV\", \"name\": \"Dover Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"FLS\", \"name\": \"Flowserve Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"GWW\", \"name\": \"W.W. Grainger Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"IR\", \"name\": \"Ingersoll Rand Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"IT\", \"name\": \"Gartner Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"J\", \"name\": \"Jacobs Solutions Inc.\", \"market\": \"US\" },\n  {\n    \"symbol\": \"LECO\",\n    \"name\": \"Lincoln Electric Holdings Inc.\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"MIDD\", \"name\": \"Middleby Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"MLI\", \"name\": \"Mueller Industries Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"NVT\", \"name\": \"nVent Electric plc\", \"market\": \"US\" },\n  { \"symbol\": \"OSK\", \"name\": \"Oshkosh Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"PWR\", \"name\": \"Quanta Services Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"RBC\", \"name\": \"Regal Rexnord Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"SNA\", \"name\": \"Snap-on Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SWK\", \"name\": \"Stanley Black & Decker Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"TT\", \"name\": \"Trane Technologies plc\", \"market\": \"US\" },\n  { \"symbol\": \"VMI\", \"name\": \"Valmont Industries Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"WMS\", \"name\": \"Advanced Drainage Systems Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"XYL\", \"name\": \"Xylem Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"0700.HK\", \"name\": \"Tencent Holdings Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"9988.HK\", \"name\": \"Alibaba Group Holding Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"3690.HK\", \"name\": \"Meituan\", \"market\": \"HK\" },\n  { \"symbol\": \"1299.HK\", \"name\": \"AIA Group Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"0005.HK\", \"name\": \"HSBC Holdings plc\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"2318.HK\",\n    \"name\": \"Ping An Insurance Group Co.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"2382.HK\",\n    \"name\": \"Sunny Optical Technology Group Co.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"1810.HK\", \"name\": \"Xiaomi Corp.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"0823.HK\",\n    \"name\": \"Link Real Estate Investment Trust\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"0001.HK\", \"name\": \"CK Hutchison Holdings Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"0027.HK\",\n    \"name\": \"Galaxy Entertainment Group Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"0011.HK\", \"name\": \"Hang Seng Bank Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"0002.HK\", \"name\": \"CLP Holdings Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"0003.HK\",\n    \"name\": \"Hong Kong & China Gas Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"0006.HK\", \"name\": \"Power Assets Holdings Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"0016.HK\",\n    \"name\": \"Sun Hung Kai Properties Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0017.HK\",\n    \"name\": \"New World Development Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"0023.HK\", \"name\": \"Bank of East Asia Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"0066.HK\", \"name\": \"MTR Corp. Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"0083.HK\", \"name\": \"Sino Land Co. Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"0101.HK\", \"name\": \"Hang Lung Properties Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"0144.HK\",\n    \"name\": \"China Merchants Port Holdings Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0151.HK\",\n    \"name\": \"Want Want China Holdings Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0175.HK\",\n    \"name\": \"Geely Automobile Holdings Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"0207.HK\", \"name\": \"Joy City Property Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"0267.HK\", \"name\": \"CITIC Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"0288.HK\", \"name\": \"WH Group Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"0322.HK\",\n    \"name\": \"China Oilfield Services Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0386.HK\",\n    \"name\": \"China Petroleum & Chemical Corp.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0393.HK\",\n    \"name\": \"China Merchants China Direct Investments Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0416.HK\",\n    \"name\": \"Kwoon Chung Bus Holdings Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"0451.HK\", \"name\": \"Xinyi Glass Holdings Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"0522.HK\",\n    \"name\": \"ASM Pacific Technology Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0575.HK\",\n    \"name\": \"Pacific Textiles Holdings Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0606.HK\",\n    \"name\": \"China Agri-Industries Holdings Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0669.HK\",\n    \"name\": \"Techtronic Industries Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0688.HK\",\n    \"name\": \"China Overseas Land & Investment Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0708.HK\",\n    \"name\": \"China Everbright International Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0762.HK\",\n    \"name\": \"China Unicom (Hong Kong) Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0808.HK\",\n    \"name\": \"China Oilfield Services Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"0836.HK\",\n    \"name\": \"China Resources Power Holdings Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"0857.HK\", \"name\": \"PetroChina Co. Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"0883.HK\", \"name\": \"CNOOC Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"0939.HK\",\n    \"name\": \"China Construction Bank Corp.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"0941.HK\", \"name\": \"China Mobile Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"0968.HK\", \"name\": \"Xinyi Solar Holdings Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"0992.HK\", \"name\": \"Lenovo Group Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"1038.HK\",\n    \"name\": \"CK Infrastructure Holdings Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"1044.HK\",\n    \"name\": \"Hengan International Group Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"1093.HK\",\n    \"name\": \"CSPC Pharmaceutical Group Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"1109.HK\", \"name\": \"China Resources Land Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"1113.HK\", \"name\": \"CK Asset Holdings Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"1137.HK\",\n    \"name\": \"Hong Kong Technology Venture Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"1177.HK\",\n    \"name\": \"Sino Biopharmaceutical Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"1208.HK\", \"name\": \"MMG Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"1211.HK\", \"name\": \"BYD Co. Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"1288.HK\",\n    \"name\": \"Agricultural Bank of China Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"1336.HK\",\n    \"name\": \"New China Life Insurance Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"1359.HK\",\n    \"name\": \"China Cinda Asset Management Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"1398.HK\",\n    \"name\": \"Industrial and Commercial Bank of China Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"1515.HK\",\n    \"name\": \"China Resources Medical Holdings Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"1585.HK\",\n    \"name\": \"Yihai International Holding Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"1658.HK\", \"name\": \"Post Holdings Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"1772.HK\", \"name\": \"Ganfeng Lithium Co. Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"1816.HK\", \"name\": \"CGN Power Co. Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"1876.HK\",\n    \"name\": \"Budweiser Brewing Company APAC Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"1928.HK\", \"name\": \"Sands China Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"1997.HK\",\n    \"name\": \"Wharf Real Estate Investment Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"2007.HK\",\n    \"name\": \"Country Garden Holdings Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"2018.HK\",\n    \"name\": \"AAC Technologies Holdings Inc.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"2020.HK\", \"name\": \"ANTA Sports Products Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"2196.HK\",\n    \"name\": \"Fuyao Glass Industry Group Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"2233.HK\", \"name\": \"West China Cement Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"2313.HK\",\n    \"name\": \"Shenzhou International Group Holdings Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"2328.HK\",\n    \"name\": \"PICC Property and Casualty Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"2331.HK\", \"name\": \"Li Ning Co. Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"2388.HK\",\n    \"name\": \"BOC Hong Kong Holdings Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"2412.HK\",\n    \"name\": \"China Merchants Bank Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"2428.HK\", \"name\": \"YTO Express Group Co. Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"2600.HK\",\n    \"name\": \"China Aluminum International Engineering Corp. Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"2628.HK\",\n    \"name\": \"China Life Insurance Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"2688.HK\", \"name\": \"ENN Energy Holdings Ltd.\", \"market\": \"HK\" },\n  { \"symbol\": \"2800.HK\", \"name\": \"Tracker Fund of Hong Kong\", \"market\": \"HK\" },\n  { \"symbol\": \"2822.HK\", \"name\": \"CSOP FTSE China A50 ETF\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"2883.HK\",\n    \"name\": \"China Oilfield Services Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"2899.HK\",\n    \"name\": \"Zijin Mining Group Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  {\n    \"symbol\": \"3328.HK\",\n    \"name\": \"Bank of Communications Co. Ltd.\",\n    \"market\": \"HK\"\n  },\n  { \"symbol\": \"3988.HK\", \"name\": \"Bank of China Ltd.\", \"market\": \"HK\" },\n  {\n    \"symbol\": \"BABA\",\n    \"name\": \"Alibaba Group Holding Ltd. ADR\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"JD\", \"name\": \"JD.com Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"PDD\", \"name\": \"PDD Holdings Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"NTES\", \"name\": \"NetEase Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"TAL\", \"name\": \"TAL Education Group ADR\", \"market\": \"US\" },\n  { \"symbol\": \"YUMC\", \"name\": \"Yum China Holdings Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ZTO\", \"name\": \"ZTO Express (Cayman) Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"BGNE\", \"name\": \"BeiGene Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"GDS\", \"name\": \"GDS Holdings Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"HUYA\", \"name\": \"HUYA Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"WB\", \"name\": \"Weibo Corp. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"IQ\", \"name\": \"iQIYI Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"MOMO\", \"name\": \"Hello Group Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"SINA\", \"name\": \"Sina Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"BIDU\", \"name\": \"Baidu Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"XPEV\", \"name\": \"XPeng Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"LI\", \"name\": \"Li Auto Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"NIO\", \"name\": \"NIO Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"EH\", \"name\": \"EHang Holdings Ltd. ADR\", \"market\": \"US\" },\n  {\n    \"symbol\": \"TME\",\n    \"name\": \"Tencent Music Entertainment Group ADR\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"DIDI\", \"name\": \"DiDi Global Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"QFIN\", \"name\": \"360 DigiTech Inc. ADR\", \"market\": \"US\" },\n  {\n    \"symbol\": \"KC\",\n    \"name\": \"Kingsoft Cloud Holdings Ltd. ADR\",\n    \"market\": \"US\"\n  },\n  {\n    \"symbol\": \"JKS\",\n    \"name\": \"JinkoSolar Holding Co. Ltd. ADR\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"SOL\", \"name\": \"Emeren Group Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"DQ\", \"name\": \"Daqo New Energy Corp. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"CAN\", \"name\": \"Canaan Inc. ADR\", \"market\": \"US\" },\n  {\n    \"symbol\": \"EDU\",\n    \"name\": \"New Oriental Education & Technology Group Inc. ADR\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"ATHM\", \"name\": \"Autohome Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"BZUN\", \"name\": \"Baozun Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"LK\", \"name\": \"Luckin Coffee Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"YJ\", \"name\": \"Yunji Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"VIOT\", \"name\": \"Viomi Technology Co. Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"WBAI\", \"name\": \"500.com Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"SFUN\", \"name\": \"Fang Holdings Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"CTRP\", \"name\": \"Trip.com Group Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"HTHT\", \"name\": \"H World Group Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"VIPS\", \"name\": \"Vipshop Holdings Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"YY\", \"name\": \"JOYY Inc. ADR\", \"market\": \"US\" },\n  {\n    \"symbol\": \"ZNH\",\n    \"name\": \"China Southern Airlines Co. Ltd. ADR\",\n    \"market\": \"US\"\n  },\n  {\n    \"symbol\": \"CEA\",\n    \"name\": \"China Eastern Airlines Corp. Ltd. ADR\",\n    \"market\": \"US\"\n  },\n  {\n    \"symbol\": \"SNP\",\n    \"name\": \"China Petroleum & Chemical Corp. ADR\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"PTR\", \"name\": \"PetroChina Co. Ltd. ADR\", \"market\": \"US\" },\n  {\n    \"symbol\": \"ACH\",\n    \"name\": \"Aluminum Corp. of China Ltd. ADR\",\n    \"market\": \"US\"\n  },\n  {\n    \"symbol\": \"SHI\",\n    \"name\": \"Sinopec Shanghai Petrochemical Co. Ltd. ADR\",\n    \"market\": \"US\"\n  },\n  {\n    \"symbol\": \"HNP\",\n    \"name\": \"Huaneng Power International Inc. ADR\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"CHA\", \"name\": \"China Telecom Corp. Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"CHL\", \"name\": \"China Mobile Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"CMCM\", \"name\": \"Cheetah Mobile Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"CJJD\", \"name\": \"China Jo-Jo Drugstores Inc.\", \"market\": \"US\" },\n  {\n    \"symbol\": \"CNTF\",\n    \"name\": \"China TechFaith Wireless Communication Technology Ltd.\",\n    \"market\": \"US\"\n  },\n  {\n    \"symbol\": \"CNET\",\n    \"name\": \"ZW Data Action Technologies Inc.\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"CTK\", \"name\": \"CooTek (Cayman) Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"DOGZ\", \"name\": \"Dogness (International) Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"FANH\", \"name\": \"Fanhua Inc. ADR\", \"market\": \"US\" },\n  {\n    \"symbol\": \"GSMG\",\n    \"name\": \"Glory Star New Media Group Holdings Ltd.\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"JFIN\", \"name\": \"Jiayin Group Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"KZIA\", \"name\": \"Kazia Therapeutics Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"LIZI\", \"name\": \"Lizhi Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"MDJH\", \"name\": \"MDJM Ltd.\", \"market\": \"US\" },\n  { \"symbol\": \"MTC\", \"name\": \"MMTec Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"MY\", \"name\": \"MYR Group Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"NCTY\", \"name\": \"The9 Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"PUYI\", \"name\": \"Puyi Inc. ADR\", \"market\": \"US\" },\n  {\n    \"symbol\": \"RAAS\",\n    \"name\": \"Cloopen Group Holding Ltd. ADR\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"RENN\", \"name\": \"Renren Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"SECO\", \"name\": \"Secoo Holding Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"SISI\", \"name\": \"Shineco Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SOS\", \"name\": \"SOS Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"TC\", \"name\": \"TuanChe Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"TEDU\", \"name\": \"Tarena International Inc. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"TIGR\", \"name\": \"UP Fintech Holding Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"TOUR\", \"name\": \"Tuniu Corp. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"WAFU\", \"name\": \"Wah Fu Education Group Ltd.\", \"market\": \"US\" },\n  { \"symbol\": \"XNET\", \"name\": \"Xunlei Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"YRD\", \"name\": \"Yiren Digital Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"ZEPP\", \"name\": \"Zepp Health Corp. ADR\", \"market\": \"US\" },\n  {\n    \"symbol\": \"ZKIN\",\n    \"name\": \"ZK International Group Co. Ltd.\",\n    \"market\": \"US\"\n  },\n  { \"symbol\": \"ZLAB\", \"name\": \"Zai Lab Ltd. ADR\", \"market\": \"US\" },\n  { \"symbol\": \"ZM\", \"name\": \"Zoom Video Communications Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SNAP\", \"name\": \"Snap Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SQ\", \"name\": \"Block Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ROKU\", \"name\": \"Roku Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DOCU\", \"name\": \"DocuSign Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SHOP\", \"name\": \"Shopify Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"TWLO\", \"name\": \"Twilio Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"CRWD\", \"name\": \"CrowdStrike Holdings Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"OKTA\", \"name\": \"Okta Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ZS\", \"name\": \"Zscaler Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DDOG\", \"name\": \"Datadog Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"MDB\", \"name\": \"MongoDB Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"TEAM\", \"name\": \"Atlassian Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"FSLY\", \"name\": \"Fastly Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"NET\", \"name\": \"Cloudflare Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"PLAN\", \"name\": \"Anaplan Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ESTC\", \"name\": \"Elastic N.V.\", \"market\": \"US\" },\n  { \"symbol\": \"SMAR\", \"name\": \"Smartsheet Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"WORK\", \"name\": \"Slack Technologies Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DOCS\", \"name\": \"Doximity Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"BILL\", \"name\": \"Bill Holdings Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"U\", \"name\": \"Unity Software Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"RBLX\", \"name\": \"Roblox Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"COIN\", \"name\": \"Coinbase Global Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"HOOD\", \"name\": \"Robinhood Markets Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ABNB\", \"name\": \"Airbnb Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"LYFT\", \"name\": \"Lyft Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"UBER\", \"name\": \"Uber Technologies Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DD\", \"name\": \"DuPont de Nemours Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"VEEV\", \"name\": \"Veeva Systems Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"FTNT\", \"name\": \"Fortinet Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"PINS\", \"name\": \"Pinterest Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ETSY\", \"name\": \"Etsy Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ALGN\", \"name\": \"Align Technology Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"TDOC\", \"name\": \"Teladoc Health Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DOCS\", \"name\": \"Doximity Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"MTCH\", \"name\": \"Match Group Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SPLK\", \"name\": \"Splunk Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"WDAY\", \"name\": \"Workday Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"HUBS\", \"name\": \"HubSpot Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"TTD\", \"name\": \"The Trade Desk Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"PAYC\", \"name\": \"Paycom Software Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"NOW\", \"name\": \"ServiceNow Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SNOW\", \"name\": \"Snowflake Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ZS\", \"name\": \"Zscaler Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DDOG\", \"name\": \"Datadog Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"MDB\", \"name\": \"MongoDB Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"TEAM\", \"name\": \"Atlassian Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"FSLY\", \"name\": \"Fastly Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"NET\", \"name\": \"Cloudflare Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"PLAN\", \"name\": \"Anaplan Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ESTC\", \"name\": \"Elastic N.V.\", \"market\": \"US\" },\n  { \"symbol\": \"SMAR\", \"name\": \"Smartsheet Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"WORK\", \"name\": \"Slack Technologies Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DOCS\", \"name\": \"Doximity Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"BILL\", \"name\": \"Bill Holdings Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"U\", \"name\": \"Unity Software Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"RBLX\", \"name\": \"Roblox Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"COIN\", \"name\": \"Coinbase Global Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"HOOD\", \"name\": \"Robinhood Markets Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ABNB\", \"name\": \"Airbnb Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"LYFT\", \"name\": \"Lyft Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"UBER\", \"name\": \"Uber Technologies Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DD\", \"name\": \"DuPont de Nemours Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"VEEV\", \"name\": \"Veeva Systems Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"FTNT\", \"name\": \"Fortinet Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"PINS\", \"name\": \"Pinterest Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ETSY\", \"name\": \"Etsy Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ALGN\", \"name\": \"Align Technology Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"TDOC\", \"name\": \"Teladoc Health Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"MTCH\", \"name\": \"Match Group Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SPLK\", \"name\": \"Splunk Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"WDAY\", \"name\": \"Workday Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"HUBS\", \"name\": \"HubSpot Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"TTD\", \"name\": \"The Trade Desk Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"PAYC\", \"name\": \"Paycom Software Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"NOW\", \"name\": \"ServiceNow Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SNOW\", \"name\": \"Snowflake Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DOCU\", \"name\": \"DocuSign Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"SHOP\", \"name\": \"Shopify Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"TWLO\", \"name\": \"Twilio Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"CRWD\", \"name\": \"CrowdStrike Holdings Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"OKTA\", \"name\": \"Okta Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ZS\", \"name\": \"Zscaler Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DDOG\", \"name\": \"Datadog Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"MDB\", \"name\": \"MongoDB Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"TEAM\", \"name\": \"Atlassian Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"FSLY\", \"name\": \"Fastly Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"NET\", \"name\": \"Cloudflare Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"PLAN\", \"name\": \"Anaplan Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ESTC\", \"name\": \"Elastic N.V.\", \"market\": \"US\" },\n  { \"symbol\": \"SMAR\", \"name\": \"Smartsheet Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"WORK\", \"name\": \"Slack Technologies Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"DOCS\", \"name\": \"Doximity Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"BILL\", \"name\": \"Bill Holdings Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"U\", \"name\": \"Unity Software Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"RBLX\", \"name\": \"Roblox Corp.\", \"market\": \"US\" },\n  { \"symbol\": \"COIN\", \"name\": \"Coinbase Global Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"HOOD\", \"name\": \"Robinhood Markets Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"ABNB\", \"name\": \"Airbnb Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"LYFT\", \"name\": \"Lyft Inc.\", \"market\": \"US\" },\n  { \"symbol\": \"UBER\", \"name\": \"Uber Technologies Inc.\", \"market\": \"US\" }\n]\n"
  },
  {
    "path": "crates/story/src/fixtures/countries.json",
    "content": "[\n  { \"name\": \"Afghanistan\", \"code\": \"AF\" },\n  { \"name\": \"Åland Islands\", \"code\": \"AX\" },\n  { \"name\": \"Albania\", \"code\": \"AL\" },\n  { \"name\": \"Algeria\", \"code\": \"DZ\" },\n  { \"name\": \"American Samoa\", \"code\": \"AS\" },\n  { \"name\": \"AndorrA\", \"code\": \"AD\" },\n  { \"name\": \"Angola\", \"code\": \"AO\" },\n  { \"name\": \"Anguilla\", \"code\": \"AI\" },\n  { \"name\": \"Antarctica\", \"code\": \"AQ\" },\n  { \"name\": \"Antigua and Barbuda\", \"code\": \"AG\" },\n  { \"name\": \"Argentina\", \"code\": \"AR\" },\n  { \"name\": \"Armenia\", \"code\": \"AM\" },\n  { \"name\": \"Aruba\", \"code\": \"AW\" },\n  { \"name\": \"Australia\", \"code\": \"AU\" },\n  { \"name\": \"Austria\", \"code\": \"AT\" },\n  { \"name\": \"Azerbaijan\", \"code\": \"AZ\" },\n  { \"name\": \"Bahamas\", \"code\": \"BS\" },\n  { \"name\": \"Bahrain\", \"code\": \"BH\" },\n  { \"name\": \"Bangladesh\", \"code\": \"BD\" },\n  { \"name\": \"Barbados\", \"code\": \"BB\" },\n  { \"name\": \"Belarus\", \"code\": \"BY\" },\n  { \"name\": \"Belgium\", \"code\": \"BE\" },\n  { \"name\": \"Belize\", \"code\": \"BZ\" },\n  { \"name\": \"Benin\", \"code\": \"BJ\" },\n  { \"name\": \"Bermuda\", \"code\": \"BM\" },\n  { \"name\": \"Bhutan\", \"code\": \"BT\" },\n  { \"name\": \"Bolivia\", \"code\": \"BO\" },\n  { \"name\": \"Bosnia and Herzegovina\", \"code\": \"BA\" },\n  { \"name\": \"Botswana\", \"code\": \"BW\" },\n  { \"name\": \"Bouvet Island\", \"code\": \"BV\" },\n  { \"name\": \"Brazil\", \"code\": \"BR\" },\n  { \"name\": \"British Indian Ocean Territory\", \"code\": \"IO\" },\n  { \"name\": \"Brunei Darussalam\", \"code\": \"BN\" },\n  { \"name\": \"Bulgaria\", \"code\": \"BG\" },\n  { \"name\": \"Burkina Faso\", \"code\": \"BF\" },\n  { \"name\": \"Burundi\", \"code\": \"BI\" },\n  { \"name\": \"Cambodia\", \"code\": \"KH\" },\n  { \"name\": \"Cameroon\", \"code\": \"CM\" },\n  { \"name\": \"Canada\", \"code\": \"CA\" },\n  { \"name\": \"Cape Verde\", \"code\": \"CV\" },\n  { \"name\": \"Cayman Islands\", \"code\": \"KY\" },\n  { \"name\": \"Central African Republic\", \"code\": \"CF\" },\n  { \"name\": \"Chad\", \"code\": \"TD\" },\n  { \"name\": \"Chile\", \"code\": \"CL\" },\n  { \"name\": \"China\", \"code\": \"CN\" },\n  { \"name\": \"Christmas Island\", \"code\": \"CX\" },\n  { \"name\": \"Cocos (Keeling) Islands\", \"code\": \"CC\" },\n  { \"name\": \"Colombia\", \"code\": \"CO\" },\n  { \"name\": \"Comoros\", \"code\": \"KM\" },\n  { \"name\": \"Congo\", \"code\": \"CG\" },\n  { \"name\": \"Congo, The Democratic Republic of the\", \"code\": \"CD\" },\n  { \"name\": \"Cook Islands\", \"code\": \"CK\" },\n  { \"name\": \"Costa Rica\", \"code\": \"CR\" },\n  { \"name\": \"Cote D'Ivoire\", \"code\": \"CI\" },\n  { \"name\": \"Croatia\", \"code\": \"HR\" },\n  { \"name\": \"Cuba\", \"code\": \"CU\" },\n  { \"name\": \"Cyprus\", \"code\": \"CY\" },\n  { \"name\": \"Czech Republic\", \"code\": \"CZ\" },\n  { \"name\": \"Denmark\", \"code\": \"DK\" },\n  { \"name\": \"Djibouti\", \"code\": \"DJ\" },\n  { \"name\": \"Dominica\", \"code\": \"DM\" },\n  { \"name\": \"Dominican Republic\", \"code\": \"DO\" },\n  { \"name\": \"Ecuador\", \"code\": \"EC\" },\n  { \"name\": \"Egypt\", \"code\": \"EG\" },\n  { \"name\": \"El Salvador\", \"code\": \"SV\" },\n  { \"name\": \"Equatorial Guinea\", \"code\": \"GQ\" },\n  { \"name\": \"Eritrea\", \"code\": \"ER\" },\n  { \"name\": \"Estonia\", \"code\": \"EE\" },\n  { \"name\": \"Ethiopia\", \"code\": \"ET\" },\n  { \"name\": \"Falkland Islands (Malvinas)\", \"code\": \"FK\" },\n  { \"name\": \"Faroe Islands\", \"code\": \"FO\" },\n  { \"name\": \"Fiji\", \"code\": \"FJ\" },\n  { \"name\": \"Finland\", \"code\": \"FI\" },\n  { \"name\": \"France\", \"code\": \"FR\" },\n  { \"name\": \"French Guiana\", \"code\": \"GF\" },\n  { \"name\": \"French Polynesia\", \"code\": \"PF\" },\n  { \"name\": \"French Southern Territories\", \"code\": \"TF\" },\n  { \"name\": \"Gabon\", \"code\": \"GA\" },\n  { \"name\": \"Gambia\", \"code\": \"GM\" },\n  { \"name\": \"Georgia\", \"code\": \"GE\" },\n  { \"name\": \"Germany\", \"code\": \"DE\" },\n  { \"name\": \"Ghana\", \"code\": \"GH\" },\n  { \"name\": \"Gibraltar\", \"code\": \"GI\" },\n  { \"name\": \"Greece\", \"code\": \"GR\" },\n  { \"name\": \"Greenland\", \"code\": \"GL\" },\n  { \"name\": \"Grenada\", \"code\": \"GD\" },\n  { \"name\": \"Guadeloupe\", \"code\": \"GP\" },\n  { \"name\": \"Guam\", \"code\": \"GU\" },\n  { \"name\": \"Guatemala\", \"code\": \"GT\" },\n  { \"name\": \"Guernsey\", \"code\": \"GG\" },\n  { \"name\": \"Guinea\", \"code\": \"GN\" },\n  { \"name\": \"Guinea-Bissau\", \"code\": \"GW\" },\n  { \"name\": \"Guyana\", \"code\": \"GY\" },\n  { \"name\": \"Haiti\", \"code\": \"HT\" },\n  { \"name\": \"Heard Island and Mcdonald Islands\", \"code\": \"HM\" },\n  { \"name\": \"Holy See (Vatican City State)\", \"code\": \"VA\" },\n  { \"name\": \"Honduras\", \"code\": \"HN\" },\n  { \"name\": \"Hong Kong\", \"code\": \"HK\" },\n  { \"name\": \"Hungary\", \"code\": \"HU\" },\n  { \"name\": \"Iceland\", \"code\": \"IS\" },\n  { \"name\": \"India\", \"code\": \"IN\" },\n  { \"name\": \"Indonesia\", \"code\": \"ID\" },\n  { \"name\": \"Iran, Islamic Republic Of\", \"code\": \"IR\" },\n  { \"name\": \"Iraq\", \"code\": \"IQ\" },\n  { \"name\": \"Ireland\", \"code\": \"IE\" },\n  { \"name\": \"Isle of Man\", \"code\": \"IM\" },\n  { \"name\": \"Israel\", \"code\": \"IL\" },\n  { \"name\": \"Italy\", \"code\": \"IT\" },\n  { \"name\": \"Jamaica\", \"code\": \"JM\" },\n  { \"name\": \"Japan\", \"code\": \"JP\" },\n  { \"name\": \"Jersey\", \"code\": \"JE\" },\n  { \"name\": \"Jordan\", \"code\": \"JO\" },\n  { \"name\": \"Kazakhstan\", \"code\": \"KZ\" },\n  { \"name\": \"Kenya\", \"code\": \"KE\" },\n  { \"name\": \"Kiribati\", \"code\": \"KI\" },\n  { \"name\": \"Korea, Democratic People'S Republic of\", \"code\": \"KP\" },\n  { \"name\": \"Korea, Republic of\", \"code\": \"KR\" },\n  { \"name\": \"Kuwait\", \"code\": \"KW\" },\n  { \"name\": \"Kyrgyzstan\", \"code\": \"KG\" },\n  { \"name\": \"Lao People'S Democratic Republic\", \"code\": \"LA\" },\n  { \"name\": \"Latvia\", \"code\": \"LV\" },\n  { \"name\": \"Lebanon\", \"code\": \"LB\" },\n  { \"name\": \"Lesotho\", \"code\": \"LS\" },\n  { \"name\": \"Liberia\", \"code\": \"LR\" },\n  { \"name\": \"Libyan Arab Jamahiriya\", \"code\": \"LY\" },\n  { \"name\": \"Liechtenstein\", \"code\": \"LI\" },\n  { \"name\": \"Lithuania\", \"code\": \"LT\" },\n  { \"name\": \"Luxembourg\", \"code\": \"LU\" },\n  { \"name\": \"Macao\", \"code\": \"MO\" },\n  { \"name\": \"Macedonia, The Former Yugoslav Republic of\", \"code\": \"MK\" },\n  { \"name\": \"Madagascar\", \"code\": \"MG\" },\n  { \"name\": \"Malawi\", \"code\": \"MW\" },\n  { \"name\": \"Malaysia\", \"code\": \"MY\" },\n  { \"name\": \"Maldives\", \"code\": \"MV\" },\n  { \"name\": \"Mali\", \"code\": \"ML\" },\n  { \"name\": \"Malta\", \"code\": \"MT\" },\n  { \"name\": \"Marshall Islands\", \"code\": \"MH\" },\n  { \"name\": \"Martinique\", \"code\": \"MQ\" },\n  { \"name\": \"Mauritania\", \"code\": \"MR\" },\n  { \"name\": \"Mauritius\", \"code\": \"MU\" },\n  { \"name\": \"Mayotte\", \"code\": \"YT\" },\n  { \"name\": \"Mexico\", \"code\": \"MX\" },\n  { \"name\": \"Micronesia, Federated States of\", \"code\": \"FM\" },\n  { \"name\": \"Moldova, Republic of\", \"code\": \"MD\" },\n  { \"name\": \"Monaco\", \"code\": \"MC\" },\n  { \"name\": \"Mongolia\", \"code\": \"MN\" },\n  { \"name\": \"Montserrat\", \"code\": \"MS\" },\n  { \"name\": \"Morocco\", \"code\": \"MA\" },\n  { \"name\": \"Mozambique\", \"code\": \"MZ\" },\n  { \"name\": \"Myanmar\", \"code\": \"MM\" },\n  { \"name\": \"Namibia\", \"code\": \"NA\" },\n  { \"name\": \"Nauru\", \"code\": \"NR\" },\n  { \"name\": \"Nepal\", \"code\": \"NP\" },\n  { \"name\": \"Netherlands\", \"code\": \"NL\" },\n  { \"name\": \"Netherlands Antilles\", \"code\": \"AN\" },\n  { \"name\": \"New Caledonia\", \"code\": \"NC\" },\n  { \"name\": \"New Zealand\", \"code\": \"NZ\" },\n  { \"name\": \"Nicaragua\", \"code\": \"NI\" },\n  { \"name\": \"Niger\", \"code\": \"NE\" },\n  { \"name\": \"Nigeria\", \"code\": \"NG\" },\n  { \"name\": \"Niue\", \"code\": \"NU\" },\n  { \"name\": \"Norfolk Island\", \"code\": \"NF\" },\n  { \"name\": \"Northern Mariana Islands\", \"code\": \"MP\" },\n  { \"name\": \"Norway\", \"code\": \"NO\" },\n  { \"name\": \"Oman\", \"code\": \"OM\" },\n  { \"name\": \"Pakistan\", \"code\": \"PK\" },\n  { \"name\": \"Palau\", \"code\": \"PW\" },\n  { \"name\": \"Palestinian Territory, Occupied\", \"code\": \"PS\" },\n  { \"name\": \"Panama\", \"code\": \"PA\" },\n  { \"name\": \"Papua New Guinea\", \"code\": \"PG\" },\n  { \"name\": \"Paraguay\", \"code\": \"PY\" },\n  { \"name\": \"Peru\", \"code\": \"PE\" },\n  { \"name\": \"Philippines\", \"code\": \"PH\" },\n  { \"name\": \"Pitcairn\", \"code\": \"PN\" },\n  { \"name\": \"Poland\", \"code\": \"PL\" },\n  { \"name\": \"Portugal\", \"code\": \"PT\" },\n  { \"name\": \"Puerto Rico\", \"code\": \"PR\" },\n  { \"name\": \"Qatar\", \"code\": \"QA\" },\n  { \"name\": \"Reunion\", \"code\": \"RE\" },\n  { \"name\": \"Romania\", \"code\": \"RO\" },\n  { \"name\": \"Russian Federation\", \"code\": \"RU\" },\n  { \"name\": \"RWANDA\", \"code\": \"RW\" },\n  { \"name\": \"Saint Helena\", \"code\": \"SH\" },\n  { \"name\": \"Saint Kitts and Nevis\", \"code\": \"KN\" },\n  { \"name\": \"Saint Lucia\", \"code\": \"LC\" },\n  { \"name\": \"Saint Pierre and Miquelon\", \"code\": \"PM\" },\n  { \"name\": \"Saint Vincent and the Grenadines\", \"code\": \"VC\" },\n  { \"name\": \"Samoa\", \"code\": \"WS\" },\n  { \"name\": \"San Marino\", \"code\": \"SM\" },\n  { \"name\": \"Sao Tome and Principe\", \"code\": \"ST\" },\n  { \"name\": \"Saudi Arabia\", \"code\": \"SA\" },\n  { \"name\": \"Senegal\", \"code\": \"SN\" },\n  { \"name\": \"Serbia and Montenegro\", \"code\": \"CS\" },\n  { \"name\": \"Seychelles\", \"code\": \"SC\" },\n  { \"name\": \"Sierra Leone\", \"code\": \"SL\" },\n  { \"name\": \"Singapore\", \"code\": \"SG\" },\n  { \"name\": \"Slovakia\", \"code\": \"SK\" },\n  { \"name\": \"Slovenia\", \"code\": \"SI\" },\n  { \"name\": \"Solomon Islands\", \"code\": \"SB\" },\n  { \"name\": \"Somalia\", \"code\": \"SO\" },\n  { \"name\": \"South Africa\", \"code\": \"ZA\" },\n  { \"name\": \"South Georgia and the South Sandwich Islands\", \"code\": \"GS\" },\n  { \"name\": \"Spain\", \"code\": \"ES\" },\n  { \"name\": \"Sri Lanka\", \"code\": \"LK\" },\n  { \"name\": \"Sudan\", \"code\": \"SD\" },\n  { \"name\": \"Suriname\", \"code\": \"SR\" },\n  { \"name\": \"Svalbard and Jan Mayen\", \"code\": \"SJ\" },\n  { \"name\": \"Swaziland\", \"code\": \"SZ\" },\n  { \"name\": \"Sweden\", \"code\": \"SE\" },\n  { \"name\": \"Switzerland\", \"code\": \"CH\" },\n  { \"name\": \"Syrian Arab Republic\", \"code\": \"SY\" },\n  { \"name\": \"Tajikistan\", \"code\": \"TJ\" },\n  { \"name\": \"Tanzania, United Republic of\", \"code\": \"TZ\" },\n  { \"name\": \"Thailand\", \"code\": \"TH\" },\n  { \"name\": \"Timor-Leste\", \"code\": \"TL\" },\n  { \"name\": \"Togo\", \"code\": \"TG\" },\n  { \"name\": \"Tokelau\", \"code\": \"TK\" },\n  { \"name\": \"Tonga\", \"code\": \"TO\" },\n  { \"name\": \"Trinidad and Tobago\", \"code\": \"TT\" },\n  { \"name\": \"Tunisia\", \"code\": \"TN\" },\n  { \"name\": \"Turkey\", \"code\": \"TR\" },\n  { \"name\": \"Turkmenistan\", \"code\": \"TM\" },\n  { \"name\": \"Turks and Caicos Islands\", \"code\": \"TC\" },\n  { \"name\": \"Tuvalu\", \"code\": \"TV\" },\n  { \"name\": \"Uganda\", \"code\": \"UG\" },\n  { \"name\": \"Ukraine\", \"code\": \"UA\" },\n  { \"name\": \"United Arab Emirates\", \"code\": \"AE\" },\n  { \"name\": \"United Kingdom\", \"code\": \"GB\" },\n  { \"name\": \"United States\", \"code\": \"US\" },\n  { \"name\": \"United States Minor Outlying Islands\", \"code\": \"UM\" },\n  { \"name\": \"Uruguay\", \"code\": \"UY\" },\n  { \"name\": \"Uzbekistan\", \"code\": \"UZ\" },\n  { \"name\": \"Vanuatu\", \"code\": \"VU\" },\n  { \"name\": \"Venezuela\", \"code\": \"VE\" },\n  { \"name\": \"Viet Nam\", \"code\": \"VN\" },\n  { \"name\": \"Virgin Islands, British\", \"code\": \"VG\" },\n  { \"name\": \"Virgin Islands, U.S.\", \"code\": \"VI\" },\n  { \"name\": \"Wallis and Futuna\", \"code\": \"WF\" },\n  { \"name\": \"Western Sahara\", \"code\": \"EH\" },\n  { \"name\": \"Yemen\", \"code\": \"YE\" },\n  { \"name\": \"Zambia\", \"code\": \"ZM\" },\n  { \"name\": \"Zimbabwe\", \"code\": \"ZW\" }\n]\n"
  },
  {
    "path": "crates/story/src/fixtures/daily-devices.json",
    "content": "[\n  {\n    \"date\": \"Apr 1\",\n    \"desktop\": 222,\n    \"mobile\": 111,\n    \"tablet\": 67,\n    \"watch\": 28\n  },\n  {\n    \"date\": \"Apr 2\",\n    \"desktop\": 97,\n    \"mobile\": 48,\n    \"tablet\": 29,\n    \"watch\": 12\n  },\n  {\n    \"date\": \"Apr 3\",\n    \"desktop\": 167,\n    \"mobile\": 84,\n    \"tablet\": 50,\n    \"watch\": 21\n  },\n  {\n    \"date\": \"Apr 4\",\n    \"desktop\": 242,\n    \"mobile\": 121,\n    \"tablet\": 73,\n    \"watch\": 30\n  },\n  {\n    \"date\": \"Apr 5\",\n    \"desktop\": 373,\n    \"mobile\": 187,\n    \"tablet\": 112,\n    \"watch\": 47\n  },\n  {\n    \"date\": \"Apr 6\",\n    \"desktop\": 301,\n    \"mobile\": 151,\n    \"tablet\": 91,\n    \"watch\": 38\n  },\n  {\n    \"date\": \"Apr 7\",\n    \"desktop\": 245,\n    \"mobile\": 123,\n    \"tablet\": 74,\n    \"watch\": 31\n  },\n  {\n    \"date\": \"Apr 8\",\n    \"desktop\": 409,\n    \"mobile\": 205,\n    \"tablet\": 123,\n    \"watch\": 51\n  },\n  {\n    \"date\": \"Apr 9\",\n    \"desktop\": 59,\n    \"mobile\": 30,\n    \"tablet\": 18,\n    \"watch\": 8\n  },\n  {\n    \"date\": \"Apr 10\",\n    \"desktop\": 261,\n    \"mobile\": 131,\n    \"tablet\": 79,\n    \"watch\": 33\n  },\n  {\n    \"date\": \"Apr 11\",\n    \"desktop\": 327,\n    \"mobile\": 164,\n    \"tablet\": 98,\n    \"watch\": 41\n  },\n  {\n    \"date\": \"Apr 12\",\n    \"desktop\": 292,\n    \"mobile\": 146,\n    \"tablet\": 88,\n    \"watch\": 36\n  },\n  {\n    \"date\": \"Apr 13\",\n    \"desktop\": 342,\n    \"mobile\": 171,\n    \"tablet\": 103,\n    \"watch\": 43\n  },\n  {\n    \"date\": \"Apr 14\",\n    \"desktop\": 137,\n    \"mobile\": 69,\n    \"tablet\": 41,\n    \"watch\": 17\n  },\n  {\n    \"date\": \"Apr 15\",\n    \"desktop\": 120,\n    \"mobile\": 60,\n    \"tablet\": 36,\n    \"watch\": 15\n  },\n  {\n    \"date\": \"Apr 16\",\n    \"desktop\": 138,\n    \"mobile\": 69,\n    \"tablet\": 41,\n    \"watch\": 17\n  },\n  {\n    \"date\": \"Apr 17\",\n    \"desktop\": 446,\n    \"mobile\": 223,\n    \"tablet\": 134,\n    \"watch\": 56\n  },\n  {\n    \"date\": \"Apr 18\",\n    \"desktop\": 364,\n    \"mobile\": 182,\n    \"tablet\": 109,\n    \"watch\": 46\n  },\n  {\n    \"date\": \"Apr 19\",\n    \"desktop\": 243,\n    \"mobile\": 122,\n    \"tablet\": 73,\n    \"watch\": 30\n  },\n  {\n    \"date\": \"Apr 20\",\n    \"desktop\": 89,\n    \"mobile\": 44,\n    \"tablet\": 26,\n    \"watch\": 11\n  },\n  {\n    \"date\": \"Apr 21\",\n    \"desktop\": 137,\n    \"mobile\": 69,\n    \"tablet\": 41,\n    \"watch\": 17\n  },\n  {\n    \"date\": \"Apr 22\",\n    \"desktop\": 224,\n    \"mobile\": 112,\n    \"tablet\": 67,\n    \"watch\": 28\n  },\n  {\n    \"date\": \"Apr 23\",\n    \"desktop\": 138,\n    \"mobile\": 69,\n    \"tablet\": 41,\n    \"watch\": 17\n  },\n  {\n    \"date\": \"Apr 24\",\n    \"desktop\": 387,\n    \"mobile\": 194,\n    \"tablet\": 116,\n    \"watch\": 48\n  },\n  {\n    \"date\": \"Apr 25\",\n    \"desktop\": 215,\n    \"mobile\": 108,\n    \"tablet\": 65,\n    \"watch\": 27\n  },\n  {\n    \"date\": \"Apr 26\",\n    \"desktop\": 75,\n    \"mobile\": 38,\n    \"tablet\": 23,\n    \"watch\": 10\n  },\n  {\n    \"date\": \"Apr 27\",\n    \"desktop\": 383,\n    \"mobile\": 192,\n    \"tablet\": 115,\n    \"watch\": 48\n  },\n  {\n    \"date\": \"Apr 28\",\n    \"desktop\": 122,\n    \"mobile\": 61,\n    \"tablet\": 37,\n    \"watch\": 15\n  },\n  {\n    \"date\": \"Apr 29\",\n    \"desktop\": 315,\n    \"mobile\": 158,\n    \"tablet\": 95,\n    \"watch\": 40\n  },\n  {\n    \"date\": \"Apr 30\",\n    \"desktop\": 454,\n    \"mobile\": 227,\n    \"tablet\": 136,\n    \"watch\": 57\n  },\n  {\n    \"date\": \"May 1\",\n    \"desktop\": 165,\n    \"mobile\": 82,\n    \"tablet\": 49,\n    \"watch\": 20\n  },\n  {\n    \"date\": \"May 2\",\n    \"desktop\": 293,\n    \"mobile\": 146,\n    \"tablet\": 88,\n    \"watch\": 36\n  },\n  {\n    \"date\": \"May 3\",\n    \"desktop\": 247,\n    \"mobile\": 124,\n    \"tablet\": 74,\n    \"watch\": 31\n  },\n  {\n    \"date\": \"May 4\",\n    \"desktop\": 385,\n    \"mobile\": 192,\n    \"tablet\": 115,\n    \"watch\": 48\n  },\n  {\n    \"date\": \"May 5\",\n    \"desktop\": 481,\n    \"mobile\": 241,\n    \"tablet\": 145,\n    \"watch\": 60\n  },\n  {\n    \"date\": \"May 6\",\n    \"desktop\": 498,\n    \"mobile\": 249,\n    \"tablet\": 149,\n    \"watch\": 62\n  },\n  {\n    \"date\": \"May 7\",\n    \"desktop\": 388,\n    \"mobile\": 194,\n    \"tablet\": 116,\n    \"watch\": 48\n  },\n  {\n    \"date\": \"May 8\",\n    \"desktop\": 149,\n    \"mobile\": 74,\n    \"tablet\": 44,\n    \"watch\": 18\n  },\n  {\n    \"date\": \"May 9\",\n    \"desktop\": 227,\n    \"mobile\": 114,\n    \"tablet\": 68,\n    \"watch\": 28\n  },\n  {\n    \"date\": \"May 10\",\n    \"desktop\": 293,\n    \"mobile\": 146,\n    \"tablet\": 88,\n    \"watch\": 36\n  },\n  {\n    \"date\": \"May 11\",\n    \"desktop\": 335,\n    \"mobile\": 168,\n    \"tablet\": 101,\n    \"watch\": 42\n  },\n  {\n    \"date\": \"May 12\",\n    \"desktop\": 197,\n    \"mobile\": 98,\n    \"tablet\": 59,\n    \"watch\": 24\n  },\n  {\n    \"date\": \"May 13\",\n    \"desktop\": 197,\n    \"mobile\": 98,\n    \"tablet\": 59,\n    \"watch\": 24\n  },\n  {\n    \"date\": \"May 14\",\n    \"desktop\": 448,\n    \"mobile\": 224,\n    \"tablet\": 134,\n    \"watch\": 56\n  },\n  {\n    \"date\": \"May 15\",\n    \"desktop\": 473,\n    \"mobile\": 236,\n    \"tablet\": 142,\n    \"watch\": 59\n  },\n  {\n    \"date\": \"May 16\",\n    \"desktop\": 338,\n    \"mobile\": 169,\n    \"tablet\": 101,\n    \"watch\": 42\n  },\n  {\n    \"date\": \"May 17\",\n    \"desktop\": 499,\n    \"mobile\": 250,\n    \"tablet\": 150,\n    \"watch\": 62\n  },\n  {\n    \"date\": \"May 18\",\n    \"desktop\": 315,\n    \"mobile\": 158,\n    \"tablet\": 95,\n    \"watch\": 40\n  },\n  {\n    \"date\": \"May 19\",\n    \"desktop\": 235,\n    \"mobile\": 118,\n    \"tablet\": 71,\n    \"watch\": 30\n  },\n  {\n    \"date\": \"May 20\",\n    \"desktop\": 177,\n    \"mobile\": 88,\n    \"tablet\": 53,\n    \"watch\": 22\n  },\n  {\n    \"date\": \"May 21\",\n    \"desktop\": 82,\n    \"mobile\": 41,\n    \"tablet\": 25,\n    \"watch\": 10\n  },\n  {\n    \"date\": \"May 22\",\n    \"desktop\": 81,\n    \"mobile\": 41,\n    \"tablet\": 25,\n    \"watch\": 10\n  },\n  {\n    \"date\": \"May 23\",\n    \"desktop\": 252,\n    \"mobile\": 126,\n    \"tablet\": 76,\n    \"watch\": 32\n  },\n  {\n    \"date\": \"May 24\",\n    \"desktop\": 294,\n    \"mobile\": 147,\n    \"tablet\": 88,\n    \"watch\": 37\n  },\n  {\n    \"date\": \"May 25\",\n    \"desktop\": 201,\n    \"mobile\": 100,\n    \"tablet\": 60,\n    \"watch\": 25\n  },\n  {\n    \"date\": \"May 26\",\n    \"desktop\": 213,\n    \"mobile\": 106,\n    \"tablet\": 64,\n    \"watch\": 26\n  },\n  {\n    \"date\": \"May 27\",\n    \"desktop\": 420,\n    \"mobile\": 210,\n    \"tablet\": 126,\n    \"watch\": 52\n  },\n  {\n    \"date\": \"May 28\",\n    \"desktop\": 233,\n    \"mobile\": 116,\n    \"tablet\": 70,\n    \"watch\": 29\n  },\n  {\n    \"date\": \"May 29\",\n    \"desktop\": 78,\n    \"mobile\": 39,\n    \"tablet\": 23,\n    \"watch\": 10\n  },\n  {\n    \"date\": \"May 30\",\n    \"desktop\": 340,\n    \"mobile\": 170,\n    \"tablet\": 102,\n    \"watch\": 42\n  },\n  {\n    \"date\": \"May 31\",\n    \"desktop\": 178,\n    \"mobile\": 89,\n    \"tablet\": 53,\n    \"watch\": 22\n  },\n  {\n    \"date\": \"Jun 1\",\n    \"desktop\": 178,\n    \"mobile\": 89,\n    \"tablet\": 53,\n    \"watch\": 22\n  },\n  {\n    \"date\": \"Jun 2\",\n    \"desktop\": 470,\n    \"mobile\": 235,\n    \"tablet\": 141,\n    \"watch\": 59\n  },\n  {\n    \"date\": \"Jun 3\",\n    \"desktop\": 103,\n    \"mobile\": 52,\n    \"tablet\": 31,\n    \"watch\": 13\n  },\n  {\n    \"date\": \"Jun 4\",\n    \"desktop\": 439,\n    \"mobile\": 220,\n    \"tablet\": 132,\n    \"watch\": 55\n  },\n  {\n    \"date\": \"Jun 5\",\n    \"desktop\": 88,\n    \"mobile\": 44,\n    \"tablet\": 26,\n    \"watch\": 11\n  },\n  {\n    \"date\": \"Jun 6\",\n    \"desktop\": 294,\n    \"mobile\": 147,\n    \"tablet\": 88,\n    \"watch\": 37\n  },\n  {\n    \"date\": \"Jun 7\",\n    \"desktop\": 323,\n    \"mobile\": 162,\n    \"tablet\": 97,\n    \"watch\": 40\n  },\n  {\n    \"date\": \"Jun 8\",\n    \"desktop\": 385,\n    \"mobile\": 192,\n    \"tablet\": 115,\n    \"watch\": 48\n  },\n  {\n    \"date\": \"Jun 9\",\n    \"desktop\": 438,\n    \"mobile\": 219,\n    \"tablet\": 131,\n    \"watch\": 55\n  },\n  {\n    \"date\": \"Jun 10\",\n    \"desktop\": 155,\n    \"mobile\": 78,\n    \"tablet\": 47,\n    \"watch\": 20\n  },\n  {\n    \"date\": \"Jun 11\",\n    \"desktop\": 92,\n    \"mobile\": 46,\n    \"tablet\": 28,\n    \"watch\": 12\n  },\n  {\n    \"date\": \"Jun 12\",\n    \"desktop\": 492,\n    \"mobile\": 246,\n    \"tablet\": 148,\n    \"watch\": 62\n  },\n  {\n    \"date\": \"Jun 13\",\n    \"desktop\": 81,\n    \"mobile\": 41,\n    \"tablet\": 25,\n    \"watch\": 10\n  },\n  {\n    \"date\": \"Jun 14\",\n    \"desktop\": 426,\n    \"mobile\": 213,\n    \"tablet\": 128,\n    \"watch\": 53\n  },\n  {\n    \"date\": \"Jun 15\",\n    \"desktop\": 307,\n    \"mobile\": 154,\n    \"tablet\": 92,\n    \"watch\": 38\n  },\n  {\n    \"date\": \"Jun 16\",\n    \"desktop\": 371,\n    \"mobile\": 186,\n    \"tablet\": 112,\n    \"watch\": 46\n  },\n  {\n    \"date\": \"Jun 17\",\n    \"desktop\": 475,\n    \"mobile\": 238,\n    \"tablet\": 143,\n    \"watch\": 60\n  },\n  {\n    \"date\": \"Jun 18\",\n    \"desktop\": 107,\n    \"mobile\": 54,\n    \"tablet\": 32,\n    \"watch\": 14\n  },\n  {\n    \"date\": \"Jun 19\",\n    \"desktop\": 341,\n    \"mobile\": 171,\n    \"tablet\": 103,\n    \"watch\": 43\n  },\n  {\n    \"date\": \"Jun 20\",\n    \"desktop\": 408,\n    \"mobile\": 204,\n    \"tablet\": 122,\n    \"watch\": 51\n  },\n  {\n    \"date\": \"Jun 21\",\n    \"desktop\": 169,\n    \"mobile\": 84,\n    \"tablet\": 50,\n    \"watch\": 21\n  },\n  {\n    \"date\": \"Jun 22\",\n    \"desktop\": 317,\n    \"mobile\": 158,\n    \"tablet\": 95,\n    \"watch\": 40\n  },\n  {\n    \"date\": \"Jun 23\",\n    \"desktop\": 480,\n    \"mobile\": 240,\n    \"tablet\": 144,\n    \"watch\": 60\n  },\n  {\n    \"date\": \"Jun 24\",\n    \"desktop\": 132,\n    \"mobile\": 66,\n    \"tablet\": 40,\n    \"watch\": 16\n  },\n  {\n    \"date\": \"Jun 25\",\n    \"desktop\": 141,\n    \"mobile\": 70,\n    \"tablet\": 42,\n    \"watch\": 18\n  },\n  {\n    \"date\": \"Jun 26\",\n    \"desktop\": 434,\n    \"mobile\": 217,\n    \"tablet\": 130,\n    \"watch\": 54\n  },\n  {\n    \"date\": \"Jun 27\",\n    \"desktop\": 448,\n    \"mobile\": 224,\n    \"tablet\": 134,\n    \"watch\": 56\n  },\n  {\n    \"date\": \"Jun 28\",\n    \"desktop\": 149,\n    \"mobile\": 74,\n    \"tablet\": 44,\n    \"watch\": 18\n  },\n  {\n    \"date\": \"Jun 29\",\n    \"desktop\": 103,\n    \"mobile\": 52,\n    \"tablet\": 31,\n    \"watch\": 13\n  },\n  {\n    \"date\": \"Jun 30\",\n    \"desktop\": 446,\n    \"mobile\": 223,\n    \"tablet\": 134,\n    \"watch\": 56\n  }\n]"
  },
  {
    "path": "crates/story/src/fixtures/monthly-devices.json",
    "content": "[\n  {\n    \"month\": \"Jan\",\n    \"desktop\": 186.0,\n    \"color_alpha\": 0.5\n  },\n  {\n    \"month\": \"Feb\",\n    \"desktop\": 305.0,\n    \"color_alpha\": 0.6\n  },\n  {\n    \"month\": \"March\",\n    \"desktop\": 237.0,\n    \"color_alpha\": 0.7\n  },\n  {\n    \"month\": \"April\",\n    \"desktop\": 73.0,\n    \"color_alpha\": 0.8\n  },\n  {\n    \"month\": \"May\",\n    \"desktop\": 209.0,\n    \"color_alpha\": 0.9\n  },\n  {\n    \"month\": \"June\",\n    \"desktop\": 214.0,\n    \"color_alpha\": 1.0\n  }\n]"
  },
  {
    "path": "crates/story/src/fixtures/stock-prices.json",
    "content": "[\n  {\n    \"date\": \"Jan\",\n    \"open\": 100.0,\n    \"high\": 112.0,\n    \"low\": 95.0,\n    \"close\": 110.0\n  },\n  {\n    \"date\": \"Feb\",\n    \"open\": 110.0,\n    \"high\": 112.0,\n    \"low\": 108.0,\n    \"close\": 111.0\n  },\n  {\n    \"date\": \"Mar\",\n    \"open\": 111.0,\n    \"high\": 118.0,\n    \"low\": 110.0,\n    \"close\": 116.0\n  },\n  {\n    \"date\": \"Apr\",\n    \"open\": 116.0,\n    \"high\": 120.0,\n    \"low\": 108.0,\n    \"close\": 110.0\n  },\n  {\n    \"date\": \"May\",\n    \"open\": 110.0,\n    \"high\": 118.0,\n    \"low\": 105.0,\n    \"close\": 115.0\n  },\n  {\n    \"date\": \"Jun\",\n    \"open\": 115.0,\n    \"high\": 125.0,\n    \"low\": 113.0,\n    \"close\": 123.0\n  }\n]\n"
  },
  {
    "path": "crates/story/src/gallery.rs",
    "content": "use gpui::{prelude::*, *};\nuse gpui_component::{\n    ActiveTheme as _, Icon, IconName, h_flex,\n    input::{Input, InputEvent, InputState},\n    resizable::{h_resizable, resizable_panel},\n    sidebar::{Sidebar, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem},\n    v_flex,\n};\n\nuse crate::*;\n\npub struct Gallery {\n    stories: Vec<(&'static str, Vec<Entity<StoryContainer>>)>,\n    active_group_index: Option<usize>,\n    active_index: Option<usize>,\n    collapsed: bool,\n    search_input: Entity<InputState>,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl Gallery {\n    pub fn new(init_story: Option<&str>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let search_input = cx.new(|cx| InputState::new(window, cx).placeholder(\"Search...\"));\n        let _subscriptions = vec![cx.subscribe(&search_input, |this, _, e, cx| match e {\n            InputEvent::Change => {\n                this.active_group_index = Some(0);\n                this.active_index = Some(0);\n                cx.notify()\n            }\n            _ => {}\n        })];\n        let stories = vec![\n            (\n                \"Getting Started\",\n                vec![StoryContainer::panel::<WelcomeStory>(window, cx)],\n            ),\n            (\n                \"Components\",\n                vec![\n                    StoryContainer::panel::<AccordionStory>(window, cx),\n                    StoryContainer::panel::<AlertStory>(window, cx),\n                    StoryContainer::panel::<AlertDialogStory>(window, cx),\n                    StoryContainer::panel::<AvatarStory>(window, cx),\n                    StoryContainer::panel::<BadgeStory>(window, cx),\n                    StoryContainer::panel::<BreadcrumbStory>(window, cx),\n                    StoryContainer::panel::<ButtonStory>(window, cx),\n                    StoryContainer::panel::<CalendarStory>(window, cx),\n                    StoryContainer::panel::<ChartStory>(window, cx),\n                    StoryContainer::panel::<CheckboxStory>(window, cx),\n                    StoryContainer::panel::<ClipboardStory>(window, cx),\n                    StoryContainer::panel::<CollapsibleStory>(window, cx),\n                    StoryContainer::panel::<ColorPickerStory>(window, cx),\n                    StoryContainer::panel::<DatePickerStory>(window, cx),\n                    StoryContainer::panel::<DescriptionListStory>(window, cx),\n                    StoryContainer::panel::<DialogStory>(window, cx),\n                    StoryContainer::panel::<DividerStory>(window, cx),\n                    StoryContainer::panel::<DropdownButtonStory>(window, cx),\n                    StoryContainer::panel::<EditorStory>(window, cx),\n                    StoryContainer::panel::<FormStory>(window, cx),\n                    StoryContainer::panel::<GroupBoxStory>(window, cx),\n                    StoryContainer::panel::<HoverCardStory>(window, cx),\n                    StoryContainer::panel::<IconStory>(window, cx),\n                    StoryContainer::panel::<ImageStory>(window, cx),\n                    StoryContainer::panel::<InputStory>(window, cx),\n                    StoryContainer::panel::<KbdStory>(window, cx),\n                    StoryContainer::panel::<LabelStory>(window, cx),\n                    StoryContainer::panel::<ListStory>(window, cx),\n                    StoryContainer::panel::<MenuStory>(window, cx),\n                    StoryContainer::panel::<NotificationStory>(window, cx),\n                    StoryContainer::panel::<NumberInputStory>(window, cx),\n                    StoryContainer::panel::<OtpInputStory>(window, cx),\n                    StoryContainer::panel::<PaginationStory>(window, cx),\n                    StoryContainer::panel::<PopoverStory>(window, cx),\n                    StoryContainer::panel::<ProgressStory>(window, cx),\n                    StoryContainer::panel::<RadioStory>(window, cx),\n                    StoryContainer::panel::<RatingStory>(window, cx),\n                    StoryContainer::panel::<ResizableStory>(window, cx),\n                    StoryContainer::panel::<ScrollbarStory>(window, cx),\n                    StoryContainer::panel::<SelectStory>(window, cx),\n                    StoryContainer::panel::<SettingsStory>(window, cx),\n                    StoryContainer::panel::<SheetStory>(window, cx),\n                    StoryContainer::panel::<SidebarStory>(window, cx),\n                    StoryContainer::panel::<SkeletonStory>(window, cx),\n                    StoryContainer::panel::<SliderStory>(window, cx),\n                    StoryContainer::panel::<SpinnerStory>(window, cx),\n                    StoryContainer::panel::<StepperStory>(window, cx),\n                    StoryContainer::panel::<SwitchStory>(window, cx),\n                    StoryContainer::panel::<DataTableStory>(window, cx),\n                    StoryContainer::panel::<TableStory>(window, cx),\n                    StoryContainer::panel::<TabsStory>(window, cx),\n                    StoryContainer::panel::<TagStory>(window, cx),\n                    StoryContainer::panel::<TextareaStory>(window, cx),\n                    StoryContainer::panel::<ThemeColorsStory>(window, cx),\n                    StoryContainer::panel::<ToggleStory>(window, cx),\n                    StoryContainer::panel::<TooltipStory>(window, cx),\n                    StoryContainer::panel::<TreeStory>(window, cx),\n                    StoryContainer::panel::<VirtualListStory>(window, cx),\n                ],\n            ),\n        ];\n\n        let mut this = Self {\n            search_input,\n            stories,\n            active_group_index: Some(0),\n            active_index: Some(0),\n            collapsed: false,\n            _subscriptions,\n        };\n\n        if let Some(init_story) = init_story {\n            this.set_active_story(init_story, window, cx);\n        }\n\n        this\n    }\n\n    fn set_active_story(&mut self, name: &str, window: &mut Window, cx: &mut App) {\n        let name = name.to_string();\n        self.search_input.update(cx, |this, cx| {\n            this.set_value(&name, window, cx);\n        })\n    }\n\n    pub fn view(init_story: Option<&str>, window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(init_story, window, cx))\n    }\n}\n\nimpl Render for Gallery {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let query = self.search_input.read(cx).value().trim().to_lowercase();\n\n        let stories: Vec<_> = self\n            .stories\n            .iter()\n            .filter_map(|(name, items)| {\n                let filtered_items: Vec<_> = items\n                    .iter()\n                    .filter(|story| story.read(cx).name.to_lowercase().contains(&query))\n                    .cloned()\n                    .collect();\n\n                if !filtered_items.is_empty() {\n                    Some((name, filtered_items))\n                } else {\n                    None\n                }\n            })\n            .collect();\n\n        let active_group = self.active_group_index.and_then(|index| stories.get(index));\n        let active_story = self\n            .active_index\n            .and(active_group)\n            .and_then(|group| group.1.get(self.active_index.unwrap()));\n        let (story_name, description) =\n            if let Some(story) = active_story.as_ref().map(|story| story.read(cx)) {\n                (story.name.clone(), story.description.clone())\n            } else {\n                (\"\".into(), \"\".into())\n            };\n\n        h_resizable(\"gallery-container\")\n            .child(\n                resizable_panel()\n                    .size(px(255.))\n                    .size_range(px(200.)..px(320.))\n                    .child(\n                        Sidebar::new(\"gallery-sidebar\")\n                            .w(relative(1.))\n                            .border_0()\n                            .collapsed(self.collapsed)\n                            .header(\n                                v_flex()\n                                    .w_full()\n                                    .gap_4()\n                                    .child(\n                                        SidebarHeader::new()\n                                            .w_full()\n                                            .child(\n                                                div()\n                                                    .flex()\n                                                    .items_center()\n                                                    .justify_center()\n                                                    .rounded(cx.theme().radius_lg)\n                                                    .bg(cx.theme().primary)\n                                                    .text_color(cx.theme().primary_foreground)\n                                                    .size_8()\n                                                    .flex_shrink_0()\n                                                    .when(!self.collapsed, |this| {\n                                                        this.child(Icon::new(\n                                                            IconName::GalleryVerticalEnd,\n                                                        ))\n                                                    })\n                                                    .when(self.collapsed, |this| {\n                                                        this.size_4()\n                                                            .bg(cx.theme().transparent)\n                                                            .text_color(cx.theme().foreground)\n                                                            .child(Icon::new(\n                                                                IconName::GalleryVerticalEnd,\n                                                            ))\n                                                    }),\n                                            )\n                                            .when(!self.collapsed, |this| {\n                                                this.child(\n                                                    v_flex()\n                                                        .gap_0()\n                                                        .text_sm()\n                                                        .flex_1()\n                                                        .line_height(relative(1.25))\n                                                        .overflow_hidden()\n                                                        .text_ellipsis()\n                                                        .child(\"GPUI Component\")\n                                                        .child(\n                                                            div()\n                                                                .text_color(\n                                                                    cx.theme().muted_foreground,\n                                                                )\n                                                                .child(\"Gallery\")\n                                                                .text_xs(),\n                                                        ),\n                                                )\n                                            }),\n                                    )\n                                    .child(\n                                        div()\n                                            .bg(cx.theme().sidebar_accent)\n                                            .rounded_full()\n                                            .px_1()\n                                            .when(cx.theme().radius.is_zero(), |this| {\n                                                this.rounded(px(0.))\n                                            })\n                                            .flex_1()\n                                            .mx_1()\n                                            .child(\n                                                Input::new(&self.search_input)\n                                                    .appearance(false)\n                                                    .cleanable(true),\n                                            ),\n                                    ),\n                            )\n                            .children(stories.clone().into_iter().enumerate().map(\n                                |(group_ix, (group_name, sub_stories))| {\n                                    SidebarGroup::new(*group_name).child(\n                                        SidebarMenu::new().children(\n                                            sub_stories.iter().enumerate().map(|(ix, story)| {\n                                                SidebarMenuItem::new(story.read(cx).name.clone())\n                                                    .active(\n                                                        self.active_group_index == Some(group_ix)\n                                                            && self.active_index == Some(ix),\n                                                    )\n                                                    .on_click(cx.listener(\n                                                        move |this, _: &ClickEvent, _, cx| {\n                                                            this.active_group_index =\n                                                                Some(group_ix);\n                                                            this.active_index = Some(ix);\n                                                            cx.notify();\n                                                        },\n                                                    ))\n                                            }),\n                                        ),\n                                    )\n                                },\n                            )),\n                    ),\n            )\n            .child(\n                v_flex()\n                    .flex_1()\n                    .h_full()\n                    .overflow_x_hidden()\n                    .child(\n                        h_flex()\n                            .id(\"header\")\n                            .p_4()\n                            .border_b_1()\n                            .border_color(cx.theme().border)\n                            .justify_between()\n                            .items_start()\n                            .child(\n                                v_flex()\n                                    .gap_1()\n                                    .child(div().text_xl().child(story_name))\n                                    .child(\n                                        div()\n                                            .text_color(cx.theme().muted_foreground)\n                                            .child(description),\n                                    ),\n                            ),\n                    )\n                    .child(\n                        div()\n                            .id(\"story\")\n                            .flex_1()\n                            .overflow_y_scroll()\n                            .when_some(active_story, |this, active_story| {\n                                this.child(active_story.clone())\n                            }),\n                    )\n                    .into_any_element(),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/lib.rs",
    "content": "use gpui::{\n    Action, AnyElement, AnyView, App, AppContext, Bounds, Context, Div, Entity, EventEmitter,\n    FocusHandle, Focusable, Global, Hsla, InteractiveElement, IntoElement, KeyBinding,\n    ParentElement, Pixels, Render, RenderOnce, SharedString, Size, StyleRefinement, Styled, Window,\n    WindowBounds, WindowKind, WindowOptions, actions, div, prelude::FluentBuilder as _, px, rems,\n    size,\n};\nuse gpui_component::{\n    ActiveTheme, IconName, Root, TitleBar, WindowExt,\n    button::Button,\n    dock::{Panel, PanelControl, PanelEvent, PanelInfo, PanelState, TitleStyle, register_panel},\n    group_box::{GroupBox, GroupBoxVariants as _},\n    h_flex,\n    menu::PopupMenu,\n    notification::Notification,\n    scroll::{ScrollableElement as _, ScrollbarShow},\n    text::markdown,\n    v_flex,\n};\nuse serde::{Deserialize, Serialize};\n\nmod app_menus;\nmod embedded_themes;\nmod gallery;\nmod stories;\nmod themes;\nmod title_bar;\npub use crate::title_bar::AppTitleBar;\npub use gallery::Gallery;\npub use stories::*;\n\n#[derive(Action, Clone, PartialEq, Eq, Deserialize)]\n#[action(namespace = story, no_json)]\npub struct SelectScrollbarShow(ScrollbarShow);\n\n#[derive(Action, Clone, PartialEq, Eq, Deserialize)]\n#[action(namespace = story, no_json)]\npub struct SelectLocale(SharedString);\n\n#[derive(Action, Clone, PartialEq, Eq, Deserialize)]\n#[action(namespace = story, no_json)]\npub struct SelectFont(usize);\n\n#[derive(Action, Clone, PartialEq, Eq, Deserialize)]\n#[action(namespace = story, no_json)]\npub struct SelectRadius(usize);\n\nactions!(\n    story,\n    [\n        About,\n        Open,\n        Quit,\n        ToggleSearch,\n        TestAction,\n        Tab,\n        TabPrev,\n        ShowPanelInfo,\n        ToggleListActiveHighlight\n    ]\n);\n\nconst PANEL_NAME: &str = \"StoryContainer\";\n\npub struct AppState {\n    pub invisible_panels: Entity<Vec<SharedString>>,\n}\nimpl AppState {\n    fn init(cx: &mut App) {\n        let state = Self {\n            invisible_panels: cx.new(|_| Vec::new()),\n        };\n        cx.set_global::<AppState>(state);\n    }\n\n    pub fn global(cx: &App) -> &Self {\n        cx.global::<Self>()\n    }\n\n    pub fn global_mut(cx: &mut App) -> &mut Self {\n        cx.global_mut::<Self>()\n    }\n}\n\npub fn create_new_window<F, E>(title: &str, crate_view_fn: F, cx: &mut App)\nwhere\n    E: Into<AnyView>,\n    F: FnOnce(&mut Window, &mut App) -> E + Send + 'static,\n{\n    create_new_window_with_size(title, None, crate_view_fn, cx);\n}\n\npub fn create_new_window_with_size<F, E>(\n    title: &str,\n    window_size: Option<Size<Pixels>>,\n    crate_view_fn: F,\n    cx: &mut App,\n) where\n    E: Into<AnyView>,\n    F: FnOnce(&mut Window, &mut App) -> E + Send + 'static,\n{\n    let mut window_size = window_size.unwrap_or(size(px(1600.0), px(1200.0)));\n    if let Some(display) = cx.primary_display() {\n        let display_size = display.bounds().size;\n        window_size.width = window_size.width.min(display_size.width * 0.85);\n        window_size.height = window_size.height.min(display_size.height * 0.85);\n    }\n    let window_bounds = Bounds::centered(None, window_size, cx);\n    let title = SharedString::from(title.to_string());\n\n    cx.spawn(async move |cx| {\n        let options = WindowOptions {\n            window_bounds: Some(WindowBounds::Windowed(window_bounds)),\n            titlebar: Some(TitleBar::title_bar_options()),\n            window_min_size: Some(gpui::Size {\n                width: px(480.),\n                height: px(320.),\n            }),\n            kind: WindowKind::Normal,\n            #[cfg(target_os = \"linux\")]\n            window_background: gpui::WindowBackgroundAppearance::Transparent,\n            #[cfg(target_os = \"linux\")]\n            window_decorations: Some(gpui::WindowDecorations::Client),\n            ..Default::default()\n        };\n\n        let window = cx\n            .open_window(options, |window, cx| {\n                let view = crate_view_fn(window, cx);\n                let story_root = cx.new(|cx| StoryRoot::new(title.clone(), view, window, cx));\n\n                // Set focus to the StoryRoot to enable it's actions.\n                let focus_handle = story_root.focus_handle(cx);\n                window.defer(cx, move |window, cx| {\n                    focus_handle.focus(window, cx);\n                });\n\n                cx.new(|cx| Root::new(story_root, window, cx))\n            })\n            .expect(\"failed to open window\");\n\n        window.update(cx, |_, window, _| {\n            window.activate_window();\n            window.set_window_title(&title);\n        })?;\n\n        Ok::<_, anyhow::Error>(())\n    })\n    .detach();\n}\n\nimpl Global for AppState {}\n\npub fn init(cx: &mut App) {\n    // Try to initialize tracing subscriber, but ignore if already initialized\n    #[cfg(not(target_family = \"wasm\"))]\n    {\n        use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _};\n        let _ = tracing_subscriber::registry()\n            .with(tracing_subscriber::fmt::layer())\n            .with(\n                tracing_subscriber::EnvFilter::from_default_env()\n                    .add_directive(\"gpui_component=trace\".parse().unwrap()),\n            )\n            .try_init();\n    }\n\n    // For WASM, use a subscriber without time support\n    #[cfg(target_family = \"wasm\")]\n    {\n        use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _};\n        let _ = tracing_subscriber::registry()\n            .with(tracing_subscriber::fmt::layer().without_time())\n            .with(\n                tracing_subscriber::EnvFilter::from_default_env()\n                    .add_directive(\"gpui_component=trace\".parse().unwrap()),\n            )\n            .try_init();\n    }\n\n    gpui_component::init(cx);\n    AppState::init(cx);\n    themes::init(cx);\n    stories::init(cx);\n\n    #[cfg(not(target_family = \"wasm\"))]\n    {\n        let http_client =\n            reqwest_client::ReqwestClient::user_agent(\"gpui-component/story\").unwrap();\n        cx.set_http_client(std::sync::Arc::new(http_client));\n    }\n\n    #[cfg(target_family = \"wasm\")]\n    {\n        // Safety: the web examples run single-threaded; the client is\n        // created and used exclusively on the main thread.\n        let http_client = unsafe {\n            gpui_web::FetchHttpClient::with_user_agent(\"gpui-component/story\")\n                .expect(\"failed to create FetchHttpClient\")\n        };\n        cx.set_http_client(std::sync::Arc::new(http_client));\n    }\n\n    cx.bind_keys([\n        KeyBinding::new(\"/\", ToggleSearch, None),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-o\", Open, None),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-o\", Open, None),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-q\", Quit, None),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"alt-f4\", Quit, None),\n    ]);\n\n    cx.on_action(|_: &Quit, cx: &mut App| {\n        cx.quit();\n    });\n\n    cx.on_action(|_: &About, cx: &mut App| {\n        if let Some(window) = cx.active_window().and_then(|w| w.downcast::<Root>()) {\n            cx.defer(move |cx| {\n                window\n                    .update(cx, |_, window, cx| {\n                        window.defer(cx, |window, cx| {\n                            window.open_alert_dialog(cx, |alert, _, _| {\n                                alert.title(\"About\").description(markdown(\n                                    \"GPUI Component Storybook\\n\\n\\\n                                    Version 0.1.0\\n\\n\\\n                                    https://longbridge.github.io/gpui-component\",\n                                ))\n                            });\n                        });\n                    })\n                    .unwrap();\n            });\n        }\n    });\n\n    register_panel(cx, PANEL_NAME, |_, _, info, window, cx| {\n        let story_state = match info {\n            PanelInfo::Panel(value) => StoryState::from_value(value.clone()),\n            _ => {\n                unreachable!(\"Invalid PanelInfo: {:?}\", info)\n            }\n        };\n\n        let view = cx.new(|cx| {\n            let (title, description, closable, zoomable, story, on_active) =\n                story_state.to_story(window, cx);\n            let mut container = StoryContainer::new(window, cx)\n                .story(story, story_state.story_klass)\n                .on_active(on_active);\n\n            cx.on_focus_in(\n                &container.focus_handle,\n                window,\n                |this: &mut StoryContainer, _, _| {\n                    println!(\"StoryContainer focus in: {}\", this.name);\n                },\n            )\n            .detach();\n\n            container.name = title.into();\n            container.description = description.into();\n            container.closable = closable;\n            container.zoomable = zoomable;\n            container\n        });\n        Box::new(view)\n    });\n\n    cx.activate(true);\n}\n\n#[derive(IntoElement)]\nstruct StorySection {\n    base: Div,\n    title: SharedString,\n    sub_title: Vec<AnyElement>,\n    children: Vec<AnyElement>,\n}\n\nimpl StorySection {\n    pub fn sub_title(mut self, sub_title: impl IntoElement) -> Self {\n        self.sub_title.push(sub_title.into_any_element());\n        self\n    }\n\n    #[allow(unused)]\n    fn max_w_md(mut self) -> Self {\n        self.base = self.base.max_w(rems(48.));\n        self\n    }\n\n    #[allow(unused)]\n    fn max_w_lg(mut self) -> Self {\n        self.base = self.base.max_w(rems(64.));\n        self\n    }\n\n    #[allow(unused)]\n    fn max_w_xl(mut self) -> Self {\n        self.base = self.base.max_w(rems(80.));\n        self\n    }\n\n    #[allow(unused)]\n    fn max_w_2xl(mut self) -> Self {\n        self.base = self.base.max_w(rems(96.));\n        self\n    }\n}\n\nimpl ParentElement for StorySection {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Styled for StorySection {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        self.base.style()\n    }\n}\n\nimpl RenderOnce for StorySection {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        GroupBox::new()\n            .id(self.title.clone())\n            .outline()\n            .title(\n                h_flex()\n                    .justify_between()\n                    .w_full()\n                    .gap_4()\n                    .child(self.title)\n                    .children(self.sub_title),\n            )\n            .content_style(\n                StyleRefinement::default()\n                    .rounded(cx.theme().radius_lg)\n                    .overflow_x_hidden()\n                    .items_center()\n                    .justify_center(),\n            )\n            .child(self.base.children(self.children))\n    }\n}\n\npub(crate) fn section(title: impl Into<SharedString>) -> StorySection {\n    StorySection {\n        title: title.into(),\n        sub_title: vec![],\n        base: h_flex()\n            .w_full()\n            .flex_wrap()\n            .justify_center()\n            .items_center()\n            .gap_4(),\n        children: vec![],\n    }\n}\n\npub struct StoryContainer {\n    focus_handle: gpui::FocusHandle,\n    pub name: SharedString,\n    pub title_bg: Option<Hsla>,\n    pub description: SharedString,\n    width: Option<gpui::Pixels>,\n    height: Option<gpui::Pixels>,\n    story: Option<AnyView>,\n    story_klass: Option<SharedString>,\n    closable: bool,\n    zoomable: Option<PanelControl>,\n    paddings: Pixels,\n    on_active: Option<fn(AnyView, bool, &mut Window, &mut App)>,\n}\n\n#[derive(Debug)]\npub enum ContainerEvent {\n    Close,\n}\n\nimpl EventEmitter<ContainerEvent> for StoryContainer {}\n\nimpl StoryContainer {\n    pub fn new(_window: &mut Window, cx: &mut App) -> Self {\n        let focus_handle = cx.focus_handle();\n\n        Self {\n            focus_handle,\n            name: \"\".into(),\n            title_bg: None,\n            description: \"\".into(),\n            width: None,\n            height: None,\n            story: None,\n            story_klass: None,\n            closable: true,\n            zoomable: Some(PanelControl::default()),\n            paddings: px(16.),\n            on_active: None,\n        }\n    }\n\n    pub fn panel<S: Story>(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        let name = S::title();\n        let description = S::description();\n        let story = S::new_view(window, cx);\n        let story_klass = S::klass();\n\n        let view = cx.new(|cx| {\n            let mut story = Self::new(window, cx)\n                .story(story.into(), story_klass)\n                .on_active(S::on_active_any);\n            story.focus_handle = cx.focus_handle();\n            story.closable = S::closable();\n            story.zoomable = S::zoomable();\n            story.name = name.into();\n            story.description = description.into();\n            story.title_bg = S::title_bg();\n            story.paddings = S::paddings();\n            story\n        });\n\n        view\n    }\n\n    pub fn width(mut self, width: gpui::Pixels) -> Self {\n        self.width = Some(width);\n        self\n    }\n\n    pub fn height(mut self, height: gpui::Pixels) -> Self {\n        self.height = Some(height);\n        self\n    }\n\n    pub fn story(mut self, story: AnyView, story_klass: impl Into<SharedString>) -> Self {\n        self.story = Some(story);\n        self.story_klass = Some(story_klass.into());\n        self\n    }\n\n    pub fn on_active(mut self, on_active: fn(AnyView, bool, &mut Window, &mut App)) -> Self {\n        self.on_active = Some(on_active);\n        self\n    }\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct StoryState {\n    pub story_klass: SharedString,\n}\n\nimpl StoryState {\n    fn to_value(&self) -> serde_json::Value {\n        serde_json::json!({\n            \"story_klass\": self.story_klass,\n        })\n    }\n\n    fn from_value(value: serde_json::Value) -> Self {\n        serde_json::from_value(value).unwrap()\n    }\n\n    fn to_story(\n        &self,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (\n        &'static str,\n        &'static str,\n        bool,\n        Option<PanelControl>,\n        AnyView,\n        fn(AnyView, bool, &mut Window, &mut App),\n    ) {\n        macro_rules! story {\n            ($klass:tt) => {\n                (\n                    $klass::title(),\n                    $klass::description(),\n                    $klass::closable(),\n                    $klass::zoomable(),\n                    $klass::view(window, cx).into(),\n                    $klass::on_active_any,\n                )\n            };\n        }\n\n        match self.story_klass.to_string().as_str() {\n            \"BreadcrumbStory\" => story!(BreadcrumbStory),\n            \"ButtonStory\" => story!(ButtonStory),\n            \"CalendarStory\" => story!(CalendarStory),\n            \"SelectStory\" => story!(SelectStory),\n            \"IconStory\" => story!(IconStory),\n            \"ImageStory\" => story!(ImageStory),\n            \"InputStory\" => story!(InputStory),\n            \"ListStory\" => story!(ListStory),\n            \"DialogStory\" => story!(DialogStory),\n            \"DividerStory\" => story!(DividerStory),\n            \"PopoverStory\" => story!(PopoverStory),\n            \"ProgressStory\" => story!(ProgressStory),\n            \"ResizableStory\" => story!(ResizableStory),\n            \"ScrollbarStory\" => story!(ScrollbarStory),\n            \"SwitchStory\" => story!(SwitchStory),\n            \"DataTableStory\" => story!(DataTableStory),\n            \"TableStory\" => story!(TableStory),\n            \"LabelStory\" => story!(LabelStory),\n            \"TooltipStory\" => story!(TooltipStory),\n            \"AccordionStory\" => story!(AccordionStory),\n            \"SidebarStory\" => story!(SidebarStory),\n            \"FormStory\" => story!(FormStory),\n            \"NotificationStory\" => story!(NotificationStory),\n            \"ThemeColorsStory\" => story!(ThemeColorsStory),\n            _ => {\n                unreachable!(\"Invalid story klass: {}\", self.story_klass)\n            }\n        }\n    }\n}\n\nimpl Panel for StoryContainer {\n    fn panel_name(&self) -> &'static str {\n        \"StoryContainer\"\n    }\n\n    fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {\n        self.name.clone().into_any_element()\n    }\n\n    fn title_style(&self, cx: &App) -> Option<TitleStyle> {\n        if let Some(bg) = self.title_bg {\n            Some(TitleStyle {\n                background: bg,\n                foreground: cx.theme().foreground,\n            })\n        } else {\n            None\n        }\n    }\n\n    fn closable(&self, _cx: &App) -> bool {\n        self.closable\n    }\n\n    fn zoomable(&self, _cx: &App) -> Option<PanelControl> {\n        self.zoomable\n    }\n\n    fn visible(&self, cx: &App) -> bool {\n        !AppState::global(cx)\n            .invisible_panels\n            .read(cx)\n            .contains(&self.name)\n    }\n\n    fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, _cx: &mut Context<Self>) {\n        println!(\"panel: {} zoomed: {}\", self.name, zoomed);\n    }\n\n    fn set_active(&mut self, active: bool, _window: &mut Window, cx: &mut Context<Self>) {\n        println!(\"panel: {} active: {}\", self.name, active);\n        if let Some(on_active) = self.on_active {\n            if let Some(story) = self.story.clone() {\n                on_active(story, active, _window, cx);\n            }\n        }\n    }\n\n    fn dropdown_menu(\n        &mut self,\n        menu: PopupMenu,\n        _window: &mut Window,\n        _cx: &mut Context<Self>,\n    ) -> PopupMenu {\n        menu.menu(\"Info\", Box::new(ShowPanelInfo))\n    }\n\n    fn toolbar_buttons(\n        &mut self,\n        _window: &mut Window,\n        _cx: &mut Context<Self>,\n    ) -> Option<Vec<Button>> {\n        Some(vec![\n            Button::new(\"info\")\n                .icon(IconName::Info)\n                .on_click(|_, window, cx| {\n                    window.push_notification(\"You have clicked info button\", cx);\n                }),\n            Button::new(\"search\")\n                .icon(IconName::Search)\n                .on_click(|_, window, cx| {\n                    window.push_notification(\"You have clicked search button\", cx);\n                }),\n        ])\n    }\n\n    fn dump(&self, _cx: &App) -> PanelState {\n        let mut state = PanelState::new(self);\n        let story_state = StoryState {\n            story_klass: self.story_klass.clone().unwrap(),\n        };\n        state.info = PanelInfo::panel(story_state.to_value());\n        state\n    }\n}\n\nimpl EventEmitter<PanelEvent> for StoryContainer {}\nimpl Focusable for StoryContainer {\n    fn focus_handle(&self, _: &App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\nimpl Render for StoryContainer {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .id(\"story-container\")\n            .size_full()\n            .overflow_y_scrollbar()\n            .track_focus(&self.focus_handle)\n            .when_some(self.story.clone(), |this, story| {\n                this.child(div().size_full().p(self.paddings).child(story))\n            })\n    }\n}\n\npub struct StoryRoot {\n    pub(crate) focus_handle: FocusHandle,\n    pub(crate) title_bar: Entity<AppTitleBar>,\n    pub(crate) view: AnyView,\n}\n\nimpl StoryRoot {\n    pub fn new(\n        title: impl Into<SharedString>,\n        view: impl Into<AnyView>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Self {\n        let title_bar = cx.new(|cx| AppTitleBar::new(title, window, cx));\n        Self {\n            focus_handle: cx.focus_handle(),\n            title_bar,\n            view: view.into(),\n        }\n    }\n\n    fn on_action_panel_info(\n        &mut self,\n        _: &ShowPanelInfo,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        struct Info;\n        let note = Notification::new()\n            .message(\"You have clicked panel info.\")\n            .id::<Info>();\n        window.push_notification(note, cx);\n    }\n\n    fn on_action_toggle_search(\n        &mut self,\n        _: &ToggleSearch,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        cx.propagate();\n        if window.has_focused_input(cx) {\n            return;\n        }\n\n        struct Search;\n        let note = Notification::new()\n            .message(\"You have toggled search.\")\n            .id::<Search>();\n        window.push_notification(note, cx);\n    }\n}\n\nimpl Focusable for StoryRoot {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for StoryRoot {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let sheet_layer = Root::render_sheet_layer(window, cx);\n        let dialog_layer = Root::render_dialog_layer(window, cx);\n        let notification_layer = Root::render_notification_layer(window, cx);\n\n        div()\n            .id(\"story-root\")\n            .on_action(cx.listener(Self::on_action_panel_info))\n            .on_action(cx.listener(Self::on_action_toggle_search))\n            .size_full()\n            .child(\n                v_flex()\n                    .size_full()\n                    .child(self.title_bar.clone())\n                    .child(\n                        div()\n                            .track_focus(&self.focus_handle)\n                            .flex_1()\n                            .overflow_hidden()\n                            .child(self.view.clone()),\n                    )\n                    .children(sheet_layer)\n                    .children(dialog_layer)\n                    .children(notification_layer),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/main.rs",
    "content": "use gpui_component_assets::Assets;\nuse gpui_component_story::{Gallery, init, create_new_window};\n\nfn main() {\n    let app = gpui_platform::application().with_assets(Assets);\n\n    // Parse `cargo run -- <story_name>`\n    let name = std::env::args().nth(1);\n\n    app.run(move |cx| {\n        init(cx);\n        cx.activate(true);\n\n        create_new_window(\n            \"GPUI Component\",\n            move |window, cx| Gallery::view(name.as_deref(), window, cx),\n            cx,\n        );\n    });\n}\n"
  },
  {
    "path": "crates/story/src/stories/accordion_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement as _,\n    Render, Styled as _, Window, prelude::FluentBuilder as _,\n};\nuse gpui_component::{\n    IconName, Selectable, Sizable, Size,\n    accordion::Accordion,\n    button::{Button, ButtonGroup},\n    checkbox::Checkbox,\n    h_flex,\n    switch::Switch,\n    v_flex,\n};\n\nuse crate::section;\n\npub struct AccordionStory {\n    open_ixs: Vec<usize>,\n    size: Size,\n    bordered: bool,\n    disabled: bool,\n    multiple: bool,\n    show_icon: bool,\n    focus_handle: FocusHandle,\n}\n\nimpl super::Story for AccordionStory {\n    fn title() -> &'static str {\n        \"Accordion\"\n    }\n\n    fn description() -> &'static str {\n        \"The accordion uses collapse internally to make it collapsible.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl AccordionStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            bordered: false,\n            open_ixs: vec![0, 1, 2],\n            size: Size::default(),\n            disabled: false,\n            multiple: true,\n            show_icon: false,\n            focus_handle: cx.focus_handle(),\n        }\n    }\n\n    fn toggle_accordion(&mut self, open_ixs: Vec<usize>, _: &mut Window, cx: &mut Context<Self>) {\n        self.open_ixs = open_ixs;\n        cx.notify();\n    }\n\n    fn set_size(&mut self, size: Size, _: &mut Window, cx: &mut Context<Self>) {\n        self.size = size;\n        cx.notify();\n    }\n}\n\nimpl Focusable for AccordionStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for AccordionStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_5()\n            .child(\n                h_flex()\n                    .items_center()\n                    .justify_between()\n                    .gap_4()\n                    .flex_wrap()\n                    .child(\n                        ButtonGroup::new(\"toggle-size\")\n                            .outline()\n                            .compact()\n                            .child(\n                                Button::new(\"xsmall\")\n                                    .label(\"XSmall\")\n                                    .selected(self.size == Size::XSmall),\n                            )\n                            .child(\n                                Button::new(\"small\")\n                                    .label(\"Small\")\n                                    .selected(self.size == Size::Small),\n                            )\n                            .child(\n                                Button::new(\"medium\")\n                                    .label(\"Medium\")\n                                    .selected(self.size == Size::Medium),\n                            )\n                            .child(\n                                Button::new(\"large\")\n                                    .label(\"Large\")\n                                    .selected(self.size == Size::Large),\n                            )\n                            .on_click(cx.listener(|this, selecteds: &Vec<usize>, window, cx| {\n                                let size = match selecteds[0] {\n                                    0 => Size::XSmall,\n                                    1 => Size::Small,\n                                    2 => Size::Medium,\n                                    3 => Size::Large,\n                                    _ => unreachable!(),\n                                };\n                                this.set_size(size, window, cx);\n                            })),\n                    )\n                    .child(\n                        h_flex()\n                            .gap_2()\n                            .child(\n                                Checkbox::new(\"multiple\")\n                                    .label(\"Multiple\")\n                                    .checked(self.multiple)\n                                    .on_click(cx.listener(|this, checked, _, cx| {\n                                        this.multiple = *checked;\n                                        cx.notify();\n                                    })),\n                            )\n                            .child(\n                                Checkbox::new(\"show_icon\")\n                                    .label(\"Icon\")\n                                    .checked(self.show_icon)\n                                    .on_click(cx.listener(|this, checked, _, cx| {\n                                        this.show_icon = *checked;\n                                        cx.notify();\n                                    })),\n                            )\n                            .child(\n                                Checkbox::new(\"disabled\")\n                                    .label(\"Disabled\")\n                                    .checked(self.disabled)\n                                    .on_click(cx.listener(|this, checked, _, cx| {\n                                        this.disabled = *checked;\n                                        cx.notify();\n                                    })),\n                            )\n                            .child(\n                                Checkbox::new(\"bordered\")\n                                    .label(\"Bordered\")\n                                    .checked(self.bordered)\n                                    .on_click(cx.listener(|this, checked, _, cx| {\n                                        this.bordered = *checked;\n                                        cx.notify();\n                                    })),\n                            ),\n                    ),\n            )\n            .child(\n                section(\"Normal\").max_w_md().child(\n                    Accordion::new(\"test\")\n                        .bordered(self.bordered)\n                        .with_size(self.size)\n                        .disabled(self.disabled)\n                        .multiple(self.multiple)\n                        .item(|this| {\n                            this.open(self.open_ixs.contains(&0))\n                                .when(self.show_icon, |this| this.icon(IconName::Info))\n                                .title(\"Is it accessible?\")\n                                .child(\"Yes. It adheres to the WAI-ARIA design pattern.\")\n                        })\n                        .item(|this| {\n                            this.open(self.open_ixs.contains(&1))\n                            .when(self.show_icon, |this| this.icon(IconName::Inbox))\n                            .title(\"Is it styled with complex elements?\")\n                            .child(\n                                v_flex()\n                                    .gap_4()\n                                    .child(\n                                        \"We can put any view here, like a v_flex with a text view\",\n                                    )\n                                    .child(\n                                        h_flex()\n                                            .gap_4()\n                                            .child(Switch::new(\"switch1\").label(\"Switch\"))\n                                            .child(\n                                                Checkbox::new(\"checkbox1\").label(\"Or a Checkbox\"),\n                                            ),\n                                    ),\n                            )\n                        })\n                        .item(|this| {\n                            this.open(self.open_ixs.contains(&2))\n                                .when(self.show_icon, |this| this.icon(IconName::Moon))\n                                .title(\"This is third accordion\")\n                                .child(\n                                    \"This is the third accordion content. \\\n                                It can be any view, like a text view or a button.\",\n                                )\n                        })\n                        .on_toggle_click(cx.listener(|this, open_ixs: &[usize], window, cx| {\n                            this.toggle_accordion(open_ixs.to_vec(), window, cx);\n                        })),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/alert_dialog_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, InteractiveElement as _, IntoElement,\n    ParentElement, Render, Styled, Window, div, px,\n};\n\nuse gpui_component::{\n    ActiveTheme, Icon, IconName, StyledExt, WindowExt as _,\n    button::{Button, ButtonVariant, ButtonVariants},\n    dialog::{\n        AlertDialog, DialogAction, DialogButtonProps, DialogClose, DialogDescription, DialogFooter,\n        DialogHeader, DialogTitle,\n    },\n    v_flex,\n};\n\nuse crate::section;\n\npub struct AlertDialogStory {\n    focus_handle: FocusHandle,\n}\n\nimpl super::Story for AlertDialogStory {\n    fn title() -> &'static str {\n        \"AlertDialog\"\n    }\n\n    fn description() -> &'static str {\n        \"A modal dialog that interrupts the user with important content\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl AlertDialogStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n}\n\nimpl Focusable for AlertDialogStory {\n    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for AlertDialogStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div().id(\"alert-dialog-story\").track_focus(&self.focus_handle).size_full().child(\n            v_flex()\n                .gap_6()\n                .child(\n                    section(\"AlertDialog\").child(\n                        AlertDialog::new(cx)\n                            .p_0()\n                            .trigger(Button::new(\"info-alert\").outline().label(\"Show Info Alert\"))\n                            .on_ok(|_, window, cx| {\n                                window.push_notification(\"You have confirmed the alert\", cx);\n                                true\n                            })\n                            .on_cancel(|_, window, cx| {\n                                window.push_notification(\"Ok, you canceled the alert\", cx);\n                                true\n                            })\n                            .content(|content, _, cx| {\n                                content\n                                    .child(DialogHeader::new().p_4().child(DialogTitle::new().child(\"Are you absolutely sure?\")).child(\n                                        DialogDescription::new().child(\n                                            \"This action cannot be undone. \\\n                                            This will permanently delete your account from our servers.\",\n                                        ),\n                                    ))\n                                    .child(DialogFooter::new()\n                                        .p_4()\n                                        .border_t_1()\n                                        .border_color(cx.theme().border)\n                                        .bg(cx.theme().muted)\n                                        .child(\n                                            DialogClose::new().child(\n                                                Button::new(\"cancel\").outline().label(\"Cancel\")\n                                            )\n                                        )\n                                        .child(\n                                            DialogAction::new().child(\n                                                Button::new(\"ok\").label(\"Continue\").primary()\n                                            )\n                                        )\n                                    )\n                            }),\n                    ),\n                )\n                .child(section(\"With open_alert_dialog\").child(\n                    Button::new(\"confirm-alert\").outline().label(\"Show Confirmation\").on_click(cx.listener(\n                        |_, _, window, cx| {\n                            use gpui_component::dialog::DialogButtonProps;\n\n                            window.open_alert_dialog(cx, |alert, _, _| {\n                                alert\n                                    .title(\"Delete File\")\n                                    .description(\n                                        \"Are you sure you want to delete this file? \\\n                                                This action cannot be undone.\",\n                                    )\n                                    .button_props(\n                                        DialogButtonProps::default()\n                                            .ok_variant(ButtonVariant::Danger)\n                                            .ok_text(\"Delete\")\n                                            .cancel_text(\"Cancel\")\n                                            .show_cancel(true),\n                                    )\n                                    .on_ok(|_, window, cx| {\n                                        window.push_notification(\"File deleted\", cx);\n                                        true\n                                    })\n                            });\n                        },\n                    )),\n                ))\n                .child(section(\"With Icon\").child(\n                    AlertDialog::new(cx).w(px(320.)).trigger(\n                        Button::new(\"icon-alert\").outline().label(\"Request Permission\"),\n                    ).on_ok(|_, window, cx| {\n                        window.push_notification(\"Thank you for allowing network access\", cx);\n                        true\n                    })\n                    .content(|content, _, cx| {\n                        content\n                            .child(\n                                DialogHeader::new()\n                                    .items_center()\n                                    .child(Icon::new(IconName::TriangleAlert).size_10().text_color(cx.theme().warning))\n                                    .child(\n                                        DialogTitle::new().child(\"Network Permission Required\"),\n                                    ).child(\n                                        DialogDescription::new().child(\n                                            \"We need your permission to access the network to provide better services. \\\n                                            Please allow network access in your system settings.\",\n                                        ),\n                                    ),\n                            )\n                            .child(\n                                DialogFooter::new()\n                                    .v_flex()\n                                    .child(\n                                        DialogAction::new().child(\n                                            Button::new(\"agree\").w_full().primary().label(\"Allow\")\n                                        )\n                                    )\n                                    .child(\n                                        DialogClose::new().child(\n                                            Button::new(\"disagree\").w_full().outline().label(\"Don't Allow\")\n                                        )\n                                    )\n                            )\n                    }),\n                ))\n                .child(\n                    section(\"Destructive Action\").child(\n                        AlertDialog::new(cx)\n                            .trigger(Button::new(\"destructive-action\").outline().danger().label(\"Delete Account\"))\n                            .on_ok(|_, window, cx| {\n                                window.push_notification(\"Your account has been deleted\", cx);\n                                true\n                            })\n                            .content(|content, _, _| {\n                                content\n                                    .child(DialogHeader::new().child(DialogTitle::new().child(\"Delete Account\")).child(\n                                        DialogDescription::new().child(\n                                            \"This will permanently delete your account \\\n                                                    and all associated data. This action cannot be undone.\",\n                                        ),\n                                    ))\n                                    .child(\n                                        DialogFooter::new()\n                                            .child(\n                                                DialogClose::new().child(\n                                                    Button::new(\"cancel\").flex_1().outline().label(\"Cancel\")\n                                                )\n                                            )\n                                            .child(\n                                                DialogAction::new().child(\n                                                    Button::new(\"delete\")\n                                                        .flex_1()\n                                                        .outline()\n                                                        .danger()\n                                                        .label(\"Delete Forever\")\n                                                )\n                                            )\n                                    )\n                            }),\n                    ),\n                )\n                .child(section(\"Without Title\").child(\n                    Button::new(\"without-title\").outline().label(\"Dialog without Title\").on_click(cx.listener(\n                        |_, _, window, cx| {\n                            window.open_alert_dialog(cx, |alert, _, _| {\n                                alert\n                                    .confirm()\n                                    .child(\"This is a AlertDialog with `confirm` mode.\\\n                                        Will have OK, CANCEL buttons.\")\n                            });\n                        },\n                    )),\n                ))\n                .child(section(\"Session Timeout\").child(\n                    Button::new(\"session-timeout\").outline().label(\"Session Timeout\").on_click(cx.listener(\n                        |_, _, window, cx| {\n                            window.open_alert_dialog(cx, |alert, _, _| {\n                                alert\n                                    .on_ok(|_, window, cx| {\n                                        window.push_notification(\"Redirecting to login...\", cx);\n                                        true\n                                    })\n                                    .title(\"Session Expired\")\n                                    .description(\"Your session has expired due to inactivity.\\\n                                        Please log in again to continue.\")\n                                    .footer(\n                                        DialogFooter::new().child(\n                                            Button::new(\"sign-in\").label(\"Sign in\").primary().flex_1().on_click(\n                                                move |_, window, cx| {\n                                                    window.push_notification(\"Redirecting to login...\", cx);\n                                                    window.close_dialog(cx);\n                                                },\n                                            ),\n                                        )\n                                    )\n                            });\n                        },\n                    )),\n                ))\n                .child(section(\"Update Available\").child(\n                    AlertDialog::new(cx)\n                        .trigger(Button::new(\"update\").outline().label(\"Update Available\"))\n                        .on_cancel(|_, window, cx| {\n                            window.push_notification(\"Update postponed\", cx);\n                            true\n                        })\n                        .on_ok(|_, window, cx| {\n                            window.push_notification(\"Starting update...\", cx);\n                            true\n                        })\n                        .content(\n                        |content, _, cx| {\n                            content\n                                .child(DialogHeader::new().child(DialogTitle::new().child(\"Update Available\")).child(\n                                    DialogDescription::new().child(\n                                        \"A new version (v2.0.0) is available.\\\n                                                This update includes new features and bug fixes.\",\n                                    ),\n                                ))\n                                .child(\n                                    DialogFooter::new()\n                                        .bg(cx.theme().muted)\n                                        .child(\n                                            DialogClose::new().child(\n                                                Button::new(\"later\").flex_1().outline().label(\"Later\")\n                                            ),\n                                        )\n                                        .child(\n                                            DialogAction::new().child(\n                                                Button::new(\"update-now\").flex_1().primary().label(\"Update Now\")\n                                            )\n                                        )\n                                )\n                        },\n                    ),\n                ))\n                .child(section(\"Keyboard Disabled\").child(\n                    Button::new(\"keyboard-disabled\").outline().label(\"Keyboard Disabled\").on_click(cx.listener(\n                        |_, _, window, cx| {\n                            window.open_alert_dialog(cx, |alert, _, _| {\n                                alert\n                                    .title(\"Important Notice\")\n                                    .description(\n                                        \"Please read this important notice \\\n                                                carefully before proceeding.\",\n                                    )\n                                    .keyboard(false)\n                            });\n                        },\n                    )),\n                ))\n                .child(section(\"With confirm mode\").child(\n                    Button::new(\"overlay-closable\").outline().label(\"Confirm Mode\").on_click(cx.listener(\n                        |_, _, window, cx| {\n                            window.open_alert_dialog(cx, |alert, _, _| {\n                                alert\n                                    .confirm()\n                                    .title(\"Are you sure?\")\n                                    .child(\"This is a AlertDialog with `confirm` mode.\\\n                                        Will have OK, CANCEL buttons.\")\n                            });\n                        },\n                    )),\n                ))\n                .child(section(\"Overlay Closable\").child(\n                    Button::new(\"overlay-closable\").outline().label(\"Overlay Closable\").on_click(cx.listener(\n                        |_, _, window, cx| {\n                            window.open_alert_dialog(cx, |alert, _, _| {\n                                alert\n                                    .title(\"Overlay Closable\")\n                                    .description(\"Click outside this dialog or press ESC to close it.\")\n                                    .overlay_closable(true)\n                            });\n                        },\n                    )),\n                ))\n                .child(section(\"Prevent Close\").child(\n                    Button::new(\"prevent-close\").outline().label(\"Prevent Close\").on_click(cx.listener(\n                        |_, _, window, cx| {\n                            window.open_alert_dialog(cx, |alert, _, _| {\n                                alert\n                                    .title(\"Processing\")\n                                    .close_button(true)\n                                    .description(\n                                        \"A process is running. \\\n                                                Click Continue to stop it or Cancel to keep waiting.\",\n                                    )\n                                    .button_props(DialogButtonProps::default().ok_text(\"Continue\").show_cancel(true))\n                                    .on_ok(|_, window, cx| {\n                                        // Return false to prevent closing\n                                        window.push_notification(\"Cannot close: Process still running\", cx);\n                                        false\n                                    })\n                                    .on_cancel(|_, window, cx| {\n                                        window.push_notification(\"Waiting...\", cx);\n                                        false\n                                    })\n                            });\n                        },\n                    )),\n                ))\n        )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/alert_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render,\n    Styled, Window,\n};\nuse gpui_component::{\n    IconName, Selectable as _, Sizable as _, Size,\n    alert::Alert,\n    button::{Button, ButtonGroup},\n    dock::PanelControl,\n    text::markdown,\n    v_flex,\n};\n\nuse crate::section;\n\npub struct AlertStory {\n    size: Size,\n    banner_visible: bool,\n    focus_handle: gpui::FocusHandle,\n}\n\nimpl AlertStory {\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self { size: Size::default(), banner_visible: true, focus_handle: cx.focus_handle() }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn set_size(&mut self, size: Size, _: &mut Window, cx: &mut Context<Self>) {\n        self.size = size;\n        cx.notify();\n    }\n}\n\nimpl super::Story for AlertStory {\n    fn title() -> &'static str {\n        \"Alert\"\n    }\n\n    fn description() -> &'static str {\n        \"Displays a callout for user attention.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n\n    fn zoomable() -> Option<PanelControl> {\n        None\n    }\n}\n\nimpl Focusable for AlertStory {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for AlertStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_4()\n            .child(\n                ButtonGroup::new(\"toggle-size\")\n                    .outline()\n                    .compact()\n                    .child(\n                        Button::new(\"xsmall\").label(\"XSmall\").selected(self.size == Size::XSmall),\n                    )\n                    .child(Button::new(\"small\").label(\"Small\").selected(self.size == Size::Small))\n                    .child(\n                        Button::new(\"medium\").label(\"Medium\").selected(self.size == Size::Medium),\n                    )\n                    .child(Button::new(\"large\").label(\"Large\").selected(self.size == Size::Large))\n                    .on_click(cx.listener(|this, selecteds: &Vec<usize>, window, cx| {\n                        let size = match selecteds[0] {\n                            0 => Size::XSmall,\n                            1 => Size::Small,\n                            2 => Size::Medium,\n                            3 => Size::Large,\n                            _ => unreachable!(),\n                        };\n                        this.set_size(size, window, cx);\n                    })),\n            )\n            .child(\n                section(\"Default\").w_2_3().child(\n                    Alert::new(\n                        \"alert-default\",\n                        markdown(\n                            \"This is an alert with icon, title and description (in Markdown).\\n\\\n                            - This is a **list** item.\\n\\\n                            - This is another list item.\",\n                        ),\n                    )\n                    .with_size(self.size)\n                    .title(\"Success! Your changes have been saved\"),\n                ),\n            )\n            .child(\n                section(\"With variant\").w_2_3().child(\n                    v_flex()\n                        .w_full()\n                        .gap_3()\n                        .child(\n                            Alert::info(\"info1\", \"This is an info alert.\")\n                                .with_size(self.size)\n                                .title(\"Info message\")\n                                .on_close(cx.listener(|_, _, _, _| {\n                                    println!(\"Info alert closed\");\n                                })),\n                        )\n                        .child(\n                            Alert::success(\n                                \"success-1\",\n                                \"You have successfully submitted your form.\\n\\\n                        Thank you for your submission!\",\n                            )\n                            .with_size(self.size)\n                            .title(\"Submit Successful\"),\n                        )\n                        .child(\n                            Alert::warning(\n                                \"warning-1\",\n                                \"This is a warning alert with icon, but no title.\\n\\\n                            This is second line of text to test is the line-height is correct.\",\n                            )\n                            .with_size(self.size),\n                        )\n                        .child(\n                            Alert::error(\n                                \"error-1\",\n                                markdown(\n                                    \"Please verify your billing information and try again.\\n\\\n                            - Check your card details\\n\\\n                            - Ensure sufficient funds\\n\\\n                            - Verify billing address\",\n                                ),\n                            )\n                            .with_size(self.size)\n                            .title(\"Unable to process your payment.\"),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Banner\").w_2_3().child(\n                    v_flex()\n                        .w_full()\n                        .gap_2()\n                        .child(\n                            Alert::new(\n                                \"banner-1\",\n                                \"This is a banner alert, it will take \\\n                       the full width of the container.\",\n                            )\n                            .banner()\n                            .on_close(cx.listener(|this, _, _, cx| {\n                                this.banner_visible = !this.banner_visible;\n                                cx.notify();\n                            }))\n                            .visible(self.banner_visible)\n                            .with_size(self.size),\n                        )\n                        .child(\n                            Alert::info(\n                                \"banner-info\",\n                                \"This is a banner alert, it will take the full width of the\\\n                    container.\",\n                            )\n                            .banner()\n                            .with_size(self.size),\n                        )\n                        .child(\n                            Alert::success(\n                                \"banner-success\",\n                                \"This is a banner alert, it will take the full width of the\\\n                    container.\",\n                            )\n                            .banner()\n                            .with_size(self.size),\n                        )\n                        .child(\n                            Alert::warning(\n                                \"banner-warning\",\n                                \"This is a banner alert, it will take the full width of the\\\n                    container.\",\n                            )\n                            .banner()\n                            .with_size(self.size),\n                        )\n                        .child(\n                            Alert::error(\n                                \"banner-error\",\n                                \"This is a banner alert, it will take the full width of the\\\n                    container.\",\n                            )\n                            .banner()\n                            .with_size(self.size),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Custom Icon\").w_2_3().child(\n                    Alert::new(\n                        \"other-1\",\n                        \"Custom icon with info alert with long \\\n                    long long long long long long long long \\\n                    long long long long long long long long long \\\n                    long long messageeeeeeeee.\",\n                    )\n                    .title(\"Custom Icon\")\n                    .with_size(self.size)\n                    .icon(IconName::Calendar),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/avatar_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render,\n    Styled, Window, px,\n};\nuse gpui_component::{\n    ActiveTheme, IconName, Sizable as _, StyledExt,\n    avatar::{Avatar, AvatarGroup},\n    dock::PanelControl,\n    v_flex,\n};\n\nuse crate::section;\n\npub struct AvatarStory {\n    focus_handle: gpui::FocusHandle,\n}\n\nimpl AvatarStory {\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\n\nimpl super::Story for AvatarStory {\n    fn title() -> &'static str {\n        \"Avatar\"\n    }\n\n    fn description() -> &'static str {\n        \"Avatar is an image that represents a user or organization.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n\n    fn zoomable() -> Option<PanelControl> {\n        None\n    }\n}\n\nimpl Focusable for AvatarStory {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for AvatarStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_4()\n            .child(\n                section(\"Avatar with image\")\n                    .max_w_md()\n                    .child(\n                        Avatar::new()\n                            .name(\"Jason lee\")\n                            .src(\"https://avatars.githubusercontent.com/u/5518?v=4\")\n                            .with_size(px(100.)),\n                    )\n                    .child(\n                        Avatar::new()\n                            .src(\"https://avatars.githubusercontent.com/u/28998859?v=4\")\n                            .large(),\n                    )\n                    .child(\n                        Avatar::new()\n                            .src(\"https://avatars.githubusercontent.com/u/10757551?s=64&v=4\"),\n                    )\n                    .child(\n                        Avatar::new()\n                            .src(\"https://avatars.githubusercontent.com/u/20092316?v=4\")\n                            .small(),\n                    )\n                    .child(\n                        Avatar::new()\n                            .src(\"https://avatars.githubusercontent.com/u/150917089?v=4\")\n                            .xsmall(),\n                    ),\n            )\n            .child(\n                section(\"Avatar with text\")\n                    .max_w_md()\n                    .child(Avatar::new().name(\"Jason Lee\").large())\n                    .child(Avatar::new().name(\"Floyd Wang\"))\n                    .child(Avatar::new().name(\"xda\").small())\n                    .child(Avatar::new().name(\"ihavecoke\").xsmall()),\n            )\n            .child(\n                section(\"Placeholder\")\n                    .max_w_md()\n                    .child(Avatar::new().large())\n                    .child(Avatar::new())\n                    .child(Avatar::new().small())\n                    .child(Avatar::new().xsmall())\n                    .child(Avatar::new().placeholder(IconName::Building2)),\n            )\n            .child(\n                section(\"Avatar Group\")\n                    .v_flex()\n                    .max_w_md()\n                    .child(\n                        AvatarGroup::new()\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/5518?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/28998859?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/20092316?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/22312482?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/150917089?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/1253486?v=4\"),\n                            ),\n                    )\n                    .child(\n                        AvatarGroup::new()\n                            .small()\n                            .limit(5)\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/5518?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/28998859?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/20092316?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/22312482?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/150917089?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/20337280?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/629429?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/583231?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/1264109?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/2936367?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/1253486?v=4\"),\n                            ),\n                    )\n                    .child(\n                        AvatarGroup::new()\n                            .xsmall()\n                            .limit(6)\n                            .ellipsis()\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/5518?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/28998859?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/20092316?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/22312482?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/150917089?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/20337280?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/2936367?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/583231?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/1264109?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/10757551?v=4\"),\n                            )\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/1506323?v=4\"),\n                            ),\n                    ),\n            )\n            .child(\n                section(\"Custom rounded\").child(\n                    Avatar::new()\n                        .src(\"https://avatars.githubusercontent.com/u/5518?v=4\")\n                        .with_size(px(100.))\n                        .rounded(px(20.)),\n                ),\n            )\n            .child(\n                section(\"Custom Style\").child(\n                    Avatar::new()\n                        .src(\"https://avatars.githubusercontent.com/u/20092316?v=4\")\n                        .with_size(px(100.))\n                        .border_3()\n                        .border_color(cx.theme().foreground)\n                        .shadow_sm(),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/badge_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render,\n    Styled, Window,\n};\nuse gpui_component::{\n    avatar::Avatar, badge::Badge, dock::PanelControl, v_flex, ActiveTheme as _, Icon, IconName,\n    Sizable as _,\n};\n\nuse crate::section;\n\npub struct BadgeStory {\n    focus_handle: gpui::FocusHandle,\n}\n\nimpl BadgeStory {\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\n\nimpl super::Story for BadgeStory {\n    fn title() -> &'static str {\n        \"Badge\"\n    }\n\n    fn description() -> &'static str {\n        \"A red dot that indicates the number of unread messages.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n\n    fn zoomable() -> Option<PanelControl> {\n        None\n    }\n}\n\nimpl Focusable for BadgeStory {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for BadgeStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_4()\n            .child(\n                section(\"Badge on icon\")\n                    .max_w_md()\n                    .child(\n                        Badge::new()\n                            .count(3)\n                            .child(Icon::new(IconName::Bell).large()),\n                    )\n                    .child(\n                        Badge::new()\n                            .count(103)\n                            .child(Icon::new(IconName::Inbox).large()),\n                    ),\n            )\n            .child(\n                section(\"Badge with count\")\n                    .max_w_md()\n                    .child(Badge::new().count(3).child(\n                        Avatar::new().src(\"https://avatars.githubusercontent.com/u/5518?v=4\"),\n                    ))\n                    .child(Badge::new().count(103).child(\n                        Avatar::new().src(\"https://avatars.githubusercontent.com/u/28998859?v=4\"),\n                    )),\n            )\n            .child(\n                section(\"Badge with icon\")\n                    .max_w_md()\n                    .child(\n                        Badge::new()\n                            .icon(IconName::Check)\n                            .color(cx.theme().cyan)\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/5518?v=4\"),\n                            ),\n                    )\n                    .child(\n                        Badge::new()\n                            .icon(IconName::Star)\n                            .color(cx.theme().yellow)\n                            .child(\n                                Avatar::new()\n                                    .src(\"https://avatars.githubusercontent.com/u/20092316?v=4\"),\n                            ),\n                    ),\n            )\n            .child(\n                section(\"Badge with dot\").max_w_md().child(\n                    Badge::new().dot().count(1).child(\n                        Avatar::new().src(\"https://avatars.githubusercontent.com/u/5518?v=4\"),\n                    ),\n                ),\n            )\n            .child(\n                section(\"Badge with color\")\n                    .max_w_md()\n                    .child(Badge::new().count(3).color(cx.theme().blue).child(\n                        Avatar::new().src(\"https://avatars.githubusercontent.com/u/5518?v=4\"),\n                    ))\n                    .child(Badge::new().dot().color(cx.theme().green).count(1).child(\n                        Avatar::new().src(\"https://avatars.githubusercontent.com/u/5518?v=4\"),\n                    )),\n            )\n            .child(\n                section(\"Complex use\")\n                    .max_w_md()\n                    .child(\n                        Badge::new().count(212).large().child(\n                            Badge::new()\n                                .icon(IconName::Check)\n                                .large()\n                                .color(cx.theme().cyan)\n                                .child(\n                                    Avatar::new()\n                                        .large()\n                                        .src(\"https://avatars.githubusercontent.com/u/5518?v=4\"),\n                                ),\n                        ),\n                    )\n                    .child(\n                        Badge::new().count(2).color(cx.theme().green).large().child(\n                            Badge::new()\n                                .icon(IconName::Star)\n                                .large()\n                                .color(cx.theme().yellow)\n                                .child(\n                                    Avatar::new().large().src(\n                                        \"https://avatars.githubusercontent.com/u/20092316?v=4\",\n                                    ),\n                                ),\n                        ),\n                    )\n                    .child(\n                        Badge::new().count(3).color(cx.theme().green).child(\n                            Badge::new()\n                                .icon(IconName::Asterisk)\n                                .color(cx.theme().green)\n                                .child(\n                                    Avatar::new().src(\n                                        \"https://avatars.githubusercontent.com/u/22312482?v=4\",\n                                    ),\n                                ),\n                        ),\n                    )\n                    .child(\n                        Badge::new().dot().child(\n                            Badge::new()\n                                .icon(IconName::Sun)\n                                .small()\n                                .color(cx.theme().red)\n                                .child(\n                                    Avatar::new().small().src(\n                                        \"https://avatars.githubusercontent.com/u/150917089?v=4\",\n                                    ),\n                                ),\n                        ),\n                    ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/breadcrumb_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render,\n    Styled, Window, prelude::FluentBuilder as _,\n};\n\nuse gpui_component::{\n    breadcrumb::{Breadcrumb, BreadcrumbItem},\n    v_flex,\n};\n\nuse crate::section;\n\npub struct BreadcrumbStory {\n    focus_handle: gpui::FocusHandle,\n    clicked_item: Option<String>,\n}\n\nimpl BreadcrumbStory {\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            clicked_item: None,\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\n\nimpl super::Story for BreadcrumbStory {\n    fn title() -> &'static str {\n        \"Breadcrumb\"\n    }\n\n    fn description() -> &'static str {\n        \"A breadcrumb navigation element that shows the current location in a hierarchy.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl Focusable for BreadcrumbStory {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for BreadcrumbStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_6()\n            .child(\n                section(\"Basic Breadcrumb\").max_w_md().child(\n                    Breadcrumb::new()\n                        .child(\"Home\")\n                        .child(\"Documents\")\n                        .child(\"Projects\"),\n                ),\n            )\n            .child(\n                section(\"Click Handlers\").max_w_md().child(\n                    v_flex()\n                        .gap_4()\n                        .items_center()\n                        .child(\n                            Breadcrumb::new()\n                                .child(\"Home\")\n                                .child(BreadcrumbItem::new(\"Documents\").on_click(cx.listener(\n                                    |this, _, _, cx| {\n                                        this.clicked_item = Some(\"Documents\".to_string());\n                                        cx.notify();\n                                    },\n                                )))\n                                .child(BreadcrumbItem::new(\"Projects\").on_click(cx.listener(\n                                    |this, _, _, cx| {\n                                        this.clicked_item = Some(\"Projects\".to_string());\n                                        cx.notify();\n                                    },\n                                )))\n                                .child(BreadcrumbItem::new(\"Current\").on_click(cx.listener(\n                                    |this, _, _, cx| {\n                                        this.clicked_item = Some(\"Current\".to_string());\n                                        cx.notify();\n                                    },\n                                ))),\n                        )\n                        .when_some(self.clicked_item.clone(), |this, item| {\n                            this.child(format!(\"Clicked: {}\", item))\n                        }),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/button_story.rs",
    "content": "use gpui::{\n    Action, App, AppContext as _, Axis, ClickEvent, Context, Entity, Focusable, InteractiveElement,\n    IntoElement, ParentElement as _, Render, Styled as _, Window, prelude::FluentBuilder, px,\n};\n\nuse gpui_component::{\n    ActiveTheme, Disableable as _, Icon, IconName, Selectable as _, Sizable as _, Theme,\n    button::{Button, ButtonCustomVariant, ButtonGroup, ButtonVariants as _},\n    checkbox::Checkbox,\n    h_flex,\n    progress::ProgressCircle,\n    v_flex,\n};\nuse serde::Deserialize;\n\nuse crate::section;\n\n#[derive(Clone, Action, PartialEq, Eq, Deserialize)]\n#[action(namespace = button_story, no_json)]\nenum ButtonAction {\n    Disabled,\n    Loading,\n    Selected,\n    Compact,\n}\n\npub struct ButtonStory {\n    focus_handle: gpui::FocusHandle,\n    disabled: bool,\n    loading: bool,\n    selected: bool,\n    compact: bool,\n    toggle_multiple: bool,\n}\n\nimpl ButtonStory {\n    pub fn view(_: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self {\n            focus_handle: cx.focus_handle(),\n            disabled: false,\n            loading: false,\n            selected: false,\n            compact: false,\n            toggle_multiple: false,\n        })\n    }\n\n    fn on_click(ev: &ClickEvent, _: &mut Window, _: &mut App) {\n        println!(\"Button clicked {:?}\", ev);\n    }\n\n    fn on_hover(hovered: &bool, _: &mut Window, _: &mut App) {\n        println!(\"Button hovered {:?}\", hovered);\n    }\n}\n\nimpl super::Story for ButtonStory {\n    fn title() -> &'static str {\n        \"Button\"\n    }\n\n    fn description() -> &'static str {\n        \"Displays a button or a component that looks like a button.\"\n    }\n\n    fn closable() -> bool {\n        false\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl Focusable for ButtonStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for ButtonStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let disabled = self.disabled;\n        let loading = self.loading;\n        let selected = self.selected;\n        let compact = self.compact;\n        let toggle_multiple = self.toggle_multiple;\n\n        let custom_variant = ButtonCustomVariant::new(cx)\n            .color(cx.theme().magenta)\n            .foreground(cx.theme().magenta)\n            .hover(cx.theme().magenta.opacity(0.1))\n            .active(cx.theme().magenta);\n\n        v_flex()\n            .on_action(\n                cx.listener(|this, action: &ButtonAction, _, _| match action {\n                    ButtonAction::Disabled => this.disabled = !this.disabled,\n                    ButtonAction::Loading => this.loading = !this.loading,\n                    ButtonAction::Selected => this.selected = !this.selected,\n                    ButtonAction::Compact => this.compact = !this.compact,\n                }),\n            )\n            .gap_6()\n            .child(\n                h_flex()\n                    .gap_3()\n                    .child(\n                        Checkbox::new(\"disabled-button\")\n                            .label(\"Disabled\")\n                            .checked(self.disabled)\n                            .on_click(cx.listener(|view, _, _, cx| {\n                                view.disabled = !view.disabled;\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"loading-button\")\n                            .label(\"Loading\")\n                            .checked(self.loading)\n                            .on_click(cx.listener(|view, _, _, cx| {\n                                view.loading = !view.loading;\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"selected-button\")\n                            .label(\"Selected\")\n                            .checked(self.selected)\n                            .on_click(cx.listener(|view, _, _, cx| {\n                                view.selected = !view.selected;\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"compact-button\")\n                            .label(\"Compact\")\n                            .checked(self.compact)\n                            .on_click(cx.listener(|view, _, _, cx| {\n                                view.compact = !view.compact;\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"shadow-button\")\n                            .label(\"Shadow\")\n                            .checked(cx.theme().shadow)\n                            .on_click(cx.listener(|_, _, window, cx| {\n                                let mut theme = cx.theme().clone();\n                                theme.shadow = !theme.shadow;\n                                cx.set_global::<Theme>(theme);\n                                window.refresh();\n                            })),\n                    ),\n            )\n            .child(\n                section(\"Normal Button\")\n                    .max_w_lg()\n                    .child(\n                        Button::new(\"button-0\")\n                            .label(\"Default\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click)\n                            .on_hover(Self::on_hover),\n                    )\n                    .child(\n                        Button::new(\"button-1\")\n                            .primary()\n                            .label(\"Primary\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click)\n                            .on_hover(Self::on_hover),\n                    )\n                    .child(\n                        Button::new(\"button-2\")\n                            .secondary()\n                            .label(\"Secondary\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click)\n                            .on_hover(Self::on_hover),\n                    )\n                    .child(\n                        Button::new(\"button-4\")\n                            .danger()\n                            .label(\"Danger\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click)\n                            .on_hover(Self::on_hover),\n                    )\n                    .child(\n                        Button::new(\"button-4-warning\")\n                            .warning()\n                            .label(\"Warning\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click)\n                            .on_hover(Self::on_hover),\n                    )\n                    .child(\n                        Button::new(\"button-4-success\")\n                            .success()\n                            .label(\"Success\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click)\n                            .on_hover(Self::on_hover),\n                    )\n                    .child(\n                        Button::new(\"button-5-info\")\n                            .info()\n                            .label(\"Info\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click)\n                            .on_hover(Self::on_hover),\n                    )\n                    .child(\n                        Button::new(\"button-5-ghost\")\n                            .ghost()\n                            .label(\"Ghost\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click)\n                            .on_hover(Self::on_hover),\n                    )\n                    .child(\n                        Button::new(\"button-5-link\")\n                            .link()\n                            .label(\"Link\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click)\n                            .on_hover(Self::on_hover),\n                    )\n                    .child(\n                        Button::new(\"button-5-text\")\n                            .text()\n                            .label(\"Text\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click)\n                            .on_hover(Self::on_hover),\n                    ),\n            )\n            .child(\n                section(\"Button with Icon\")\n                    .child(\n                        Button::new(\"button-icon-1\")\n                            .outline()\n                            .label(\"Confirm\")\n                            .icon(IconName::Check)\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-icon-2\")\n                            .outline()\n                            .label(\"Abort\")\n                            .icon(IconName::Close)\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-icon-3\")\n                            .outline()\n                            .label(\"Maximize\")\n                            .icon(Icon::new(IconName::Maximize))\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-icon-4\")\n                            .child(\n                                h_flex()\n                                    .items_center()\n                                    .gap_2()\n                                    .child(\"Custom Child\")\n                                    .child(IconName::ChevronDown)\n                                    .child(IconName::Eye),\n                            )\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-icon-5-ghost\")\n                            .ghost()\n                            .icon(IconName::Check)\n                            .label(\"Confirm\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-icon-6-link\")\n                            .link()\n                            .icon(IconName::Check)\n                            .label(\"Link\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-icon-6-text\")\n                            .text()\n                            .icon(IconName::Check)\n                            .label(\"Text Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    ),\n            )\n            .child(\n                section(\"With Progress\").child(\n                    h_flex()\n                        .gap_4()\n                        .child(\n                            Button::new(\"progress-button-1\")\n                                .primary()\n                                .large()\n                                .icon(\n                                    ProgressCircle::new(\"circle-progress-1\")\n                                        .color(cx.theme().primary_foreground)\n                                        .value(25.),\n                                )\n                                .label(\"Installing...\"),\n                        )\n                        .child(\n                            Button::new(\"progress-button-2\")\n                                .icon(ProgressCircle::new(\"circle-progress-2\").value(35.))\n                                .label(\"Installing...\"),\n                        )\n                        .child(\n                            Button::new(\"progress-button-3\")\n                                .small()\n                                .icon(ProgressCircle::new(\"circle-progress-3\").value(68.))\n                                .label(\"Installing...\"),\n                        )\n                        .child(\n                            Button::new(\"progress-button-4\")\n                                .xsmall()\n                                .icon(ProgressCircle::new(\"circle-progress-4\").value(85.))\n                                .label(\"Installing...\"),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Outline Button\")\n                    .max_w_lg()\n                    .child(\n                        Button::new(\"button-outline-1\")\n                            .primary()\n                            .outline()\n                            .label(\"Primary Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-2\")\n                            .outline()\n                            .label(\"Normal Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-4-danger\")\n                            .danger()\n                            .outline()\n                            .label(\"Danger Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-4-warning\")\n                            .warning()\n                            .outline()\n                            .label(\"Warning Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-4-success\")\n                            .success()\n                            .outline()\n                            .label(\"Success Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-5-info\")\n                            .info()\n                            .outline()\n                            .label(\"Info Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-5-ghost\")\n                            .ghost()\n                            .outline()\n                            .label(\"Ghost Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-5-link\")\n                            .link()\n                            .outline()\n                            .label(\"Link Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-5-text\")\n                            .text()\n                            .outline()\n                            .label(\"Text Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    ),\n            )\n            .child(\n                section(\"With Dropdown Caret\")\n                    .max_w_lg()\n                    .child(\n                        Button::new(\"button-outline-1\")\n                            .primary()\n                            .dropdown_caret(true)\n                            .label(\"Primary Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-2\")\n                            .label(\"Default Button\")\n                            .dropdown_caret(true)\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-2\")\n                            .secondary()\n                            .label(\"Secondary Button\")\n                            .dropdown_caret(true)\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-5-ghost\")\n                            .ghost()\n                            .dropdown_caret(true)\n                            .label(\"Ghost Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-5-link\")\n                            .link()\n                            .dropdown_caret(true)\n                            .label(\"Link Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-5-text\")\n                            .outline()\n                            .small()\n                            .dropdown_caret(true)\n                            .label(\"Small Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    ),\n            )\n            .child(\n                section(\"Small Size\")\n                    .child(\n                        Button::new(\"button-6\")\n                            .label(\"Primary Button\")\n                            .icon(IconName::Check)\n                            .primary()\n                            .small()\n                            .loading(true)\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-7\")\n                            .label(\"Secondary Button\")\n                            .small()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-8\")\n                            .label(\"Danger Button\")\n                            .danger()\n                            .small()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-8-outline\")\n                            .label(\"Outline Button\")\n                            .outline()\n                            .small()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-8-ghost\")\n                            .label(\"Ghost Button\")\n                            .ghost()\n                            .small()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-8-link\")\n                            .label(\"Link Button\")\n                            .link()\n                            .small()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    ),\n            )\n            .child(\n                section(\"XSmall Size\")\n                    .child(\n                        Button::new(\"button-xs-1\")\n                            .label(\"Primary Button\")\n                            .primary()\n                            .icon(IconName::Check)\n                            .xsmall()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-xs-2\")\n                            .label(\"Secondary Button\")\n                            .xsmall()\n                            .loading(true)\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-xs-3\")\n                            .label(\"Danger Button\")\n                            .danger()\n                            .xsmall()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-xs-3-ghost\")\n                            .label(\"Ghost Button\")\n                            .ghost()\n                            .xsmall()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-xs-3-outline\")\n                            .label(\"Outline Button\")\n                            .outline()\n                            .xsmall()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-xs-3-link\")\n                            .label(\"Link Button\")\n                            .link()\n                            .xsmall()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    ),\n            )\n            .child(\n                section(\"Button Group\").child(\n                    ButtonGroup::new(\"button-group\")\n                        .outline()\n                        .disabled(disabled)\n                        .child(\n                            Button::new(\"button-one\")\n                                .label(\"One\")\n                                .disabled(disabled)\n                                .selected(selected)\n                                .when(compact, |this| this.compact())\n                                .on_click(Self::on_click),\n                        )\n                        .child(\n                            Button::new(\"button-two\")\n                                .label(\"Two\")\n                                .disabled(disabled)\n                                .selected(selected)\n                                .when(compact, |this| this.compact())\n                                .on_click(Self::on_click),\n                        )\n                        .child(\n                            Button::new(\"button-three\")\n                                .label(\"Three\")\n                                .disabled(disabled)\n                                .selected(selected)\n                                .when(compact, |this| this.compact())\n                                .on_click(Self::on_click),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Button Group (Vertical)\").child(\n                    ButtonGroup::new(\"button-group-vertical\")\n                        .outline()\n                        .layout(Axis::Vertical)\n                        .disabled(disabled)\n                        .child(\n                            Button::new(\"button-one\")\n                                .label(\"One\")\n                                .disabled(disabled)\n                                .selected(selected)\n                                .when(compact, |this| this.compact())\n                                .on_click(Self::on_click),\n                        )\n                        .child(\n                            Button::new(\"button-two\")\n                                .label(\"Two\")\n                                .disabled(disabled)\n                                .selected(selected)\n                                .when(compact, |this| this.compact())\n                                .on_click(Self::on_click),\n                        )\n                        .child(\n                            Button::new(\"button-three\")\n                                .label(\"Three\")\n                                .disabled(disabled)\n                                .selected(selected)\n                                .when(compact, |this| this.compact())\n                                .on_click(Self::on_click),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Toggle Button Group\")\n                    .sub_title(\n                        Checkbox::new(\"multiple-button\")\n                            .text_sm()\n                            .label(\"Multiple\")\n                            .checked(toggle_multiple)\n                            .on_click(cx.listener(|view, _, _, cx| {\n                                view.toggle_multiple = !view.toggle_multiple;\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        ButtonGroup::new(\"toggle-button-group\")\n                            .outline()\n                            .compact()\n                            .multiple(toggle_multiple)\n                            .child(\n                                Button::new(\"disabled-toggle-button\")\n                                    .label(\"Disabled\")\n                                    .selected(disabled),\n                            )\n                            .child(\n                                Button::new(\"loading-toggle-button\")\n                                    .label(\"Loading\")\n                                    .selected(loading),\n                            )\n                            .child(\n                                Button::new(\"selected-toggle-button\")\n                                    .label(\"Selected\")\n                                    .selected(selected),\n                            )\n                            .child(\n                                Button::new(\"compact-toggle-button\")\n                                    .label(\"Compact\")\n                                    .selected(compact),\n                            )\n                            .on_click(cx.listener(|view, selected: &Vec<usize>, _, cx| {\n                                view.disabled = selected.contains(&0);\n                                view.loading = selected.contains(&1);\n                                view.selected = selected.contains(&2);\n                                view.compact = selected.contains(&3);\n                                cx.notify();\n                            })),\n                    ),\n            )\n            .child(\n                section(\"Icon Button\")\n                    .child(\n                        Button::new(\"icon-button-primary\")\n                            .icon(IconName::Search)\n                            .loading_icon(IconName::LoaderCircle)\n                            .primary()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact()),\n                    )\n                    .child(\n                        Button::new(\"icon-button-secondary\")\n                            .icon(IconName::Info)\n                            .loading(true)\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact()),\n                    )\n                    .child(\n                        Button::new(\"icon-button-danger\")\n                            .icon(IconName::Close)\n                            .danger()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact()),\n                    )\n                    .child(\n                        Button::new(\"icon-button-small-primary\")\n                            .icon(IconName::Search)\n                            .small()\n                            .primary()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact()),\n                    )\n                    .child(\n                        Button::new(\"icon-button-outline\")\n                            .icon(IconName::Search)\n                            .outline()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact()),\n                    )\n                    .child(\n                        Button::new(\"icon-button-ghost\")\n                            .icon(IconName::ArrowLeft)\n                            .loading_icon(IconName::LoaderCircle)\n                            .ghost()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact()),\n                    ),\n            )\n            .child(\n                section(\"Icon Button\")\n                    .child(\n                        Button::new(\"icon-button-4\")\n                            .icon(IconName::Info)\n                            .small()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact()),\n                    )\n                    .child(\n                        Button::new(\"icon-button-5\")\n                            .icon(IconName::Close)\n                            .small()\n                            .danger()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact()),\n                    )\n                    .child(\n                        Button::new(\"icon-button-6\")\n                            .icon(IconName::Search)\n                            .small()\n                            .primary()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact()),\n                    )\n                    .child(\n                        Button::new(\"icon-button-7\")\n                            .icon(IconName::Info)\n                            .xsmall()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact()),\n                    )\n                    .child(\n                        Button::new(\"icon-button-8\")\n                            .icon(IconName::Close)\n                            .xsmall()\n                            .danger()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact()),\n                    )\n                    .child(\n                        Button::new(\"icon-button-9\")\n                            .icon(IconName::Heart)\n                            .size(px(24.))\n                            .ghost()\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact()),\n                    ),\n            )\n            .child(\n                section(\"Custom Button\")\n                    .child(\n                        Button::new(\"button-6-custom\")\n                            .custom(custom_variant)\n                            .label(\"Custom Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-6-custom\")\n                            .outline()\n                            .custom(custom_variant)\n                            .label(\"Outline Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    )\n                    .child(\n                        Button::new(\"button-outline-6-custom-1\")\n                            .outline()\n                            .icon(IconName::Bell)\n                            .custom(custom_variant)\n                            .label(\"Icon Button\")\n                            .disabled(disabled)\n                            .selected(selected)\n                            .loading(loading)\n                            .when(compact, |this| this.compact())\n                            .on_click(Self::on_click),\n                    ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/calendar_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement as _,\n    Render, Styled as _, Window,\n};\nuse gpui_component::{\n    calendar::{Calendar, CalendarState},\n    v_flex,\n};\n\nuse crate::section;\n\npub struct CalendarStory {\n    focus_handle: FocusHandle,\n    calendar: Entity<CalendarState>,\n    calendar_wide: Entity<CalendarState>,\n    calendar_with_disabled_matcher: Entity<CalendarState>,\n}\n\nimpl super::Story for CalendarStory {\n    fn title() -> &'static str {\n        \"Calendar\"\n    }\n\n    fn description() -> &'static str {\n        \"A calendar to select a date or date range.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl CalendarStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let calendar = cx.new(|cx| CalendarState::new(window, cx));\n        let calendar_wide = cx.new(|cx| CalendarState::new(window, cx));\n        let calendar_with_disabled_matcher =\n            cx.new(|cx| CalendarState::new(window, cx).disabled_matcher(vec![0, 3, 6]));\n\n        Self {\n            calendar,\n            calendar_wide,\n            calendar_with_disabled_matcher,\n            focus_handle: cx.focus_handle(),\n        }\n    }\n}\n\nimpl Focusable for CalendarStory {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for CalendarStory {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_3()\n            .child(\n                section(\"Normal\")\n                    .max_w_md()\n                    .child(Calendar::new(&self.calendar)),\n            )\n            .child(\n                section(\"With 3 Months\")\n                    .max_w_md()\n                    .child(Calendar::new(&self.calendar_wide).number_of_months(3)),\n            )\n            .child(\n                section(\"With Disabled matcher (Sundays, Wednesdays, Saturdays)\")\n                    .max_w_md()\n                    .child(Calendar::new(&self.calendar_with_disabled_matcher)),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/chart_story/chart_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, Hsla, IntoElement, ParentElement,\n    Render, SharedString, Styled, Window, div, linear_color_stop, linear_gradient,\n    prelude::FluentBuilder, px,\n};\nuse gpui_component::{\n    ActiveTheme, StyledExt,\n    chart::{AreaChart, BarChart, CandlestickChart, LineChart, PieChart},\n    divider::Divider,\n    dock::PanelControl,\n    h_flex, v_flex,\n};\nuse serde::Deserialize;\n\nuse super::StackedBarChart;\nuse crate::Story;\n\n#[derive(Clone, Deserialize)]\nstruct MonthlyDevice {\n    pub month: SharedString,\n    pub desktop: f64,\n    pub color_alpha: f32,\n}\n\nimpl MonthlyDevice {\n    pub fn color(&self, color: Hsla) -> Hsla {\n        color.alpha(self.color_alpha)\n    }\n}\n\n#[derive(Clone, Deserialize)]\npub struct DailyDevice {\n    pub date: SharedString,\n    pub desktop: f64,\n    pub mobile: f64,\n    pub tablet: f64,\n    pub watch: f64,\n}\n\n#[derive(Clone, Deserialize)]\npub struct StockPrice {\n    pub date: SharedString,\n    pub open: f64,\n    pub high: f64,\n    pub low: f64,\n    pub close: f64,\n}\n\npub struct ChartStory {\n    focus_handle: FocusHandle,\n    daily_devices: Vec<DailyDevice>,\n    monthly_devices: Vec<MonthlyDevice>,\n    stock_prices: Vec<StockPrice>,\n}\n\nimpl ChartStory {\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        let daily_devices = serde_json::from_str::<Vec<DailyDevice>>(include_str!(\n            \"../../fixtures/daily-devices.json\"\n        ))\n        .unwrap();\n        let monthly_devices = serde_json::from_str::<Vec<MonthlyDevice>>(include_str!(\n            \"../../fixtures/monthly-devices.json\"\n        ))\n        .unwrap();\n        let stock_prices = serde_json::from_str::<Vec<StockPrice>>(include_str!(\n            \"../../fixtures/stock-prices.json\"\n        ))\n        .unwrap();\n\n        Self {\n            daily_devices,\n            monthly_devices,\n            stock_prices,\n            focus_handle: cx.focus_handle(),\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\n\nimpl Story for ChartStory {\n    fn title() -> &'static str {\n        \"Chart\"\n    }\n\n    fn description() -> &'static str {\n        \"Beautiful Charts & Graphs.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n\n    fn zoomable() -> Option<PanelControl> {\n        None\n    }\n}\n\nimpl Focusable for ChartStory {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nfn chart_container(\n    title: &str,\n    chart: impl IntoElement,\n    center: bool,\n    cx: &mut Context<ChartStory>,\n) -> impl IntoElement {\n    v_flex()\n        .flex_1()\n        .h(px(400.))\n        .border_1()\n        .border_color(cx.theme().border)\n        .rounded(cx.theme().radius_lg)\n        .p_4()\n        .child(\n            div()\n                .when(center, |this| this.text_center())\n                .font_semibold()\n                .child(title.to_string()),\n        )\n        .child(\n            div()\n                .when(center, |this| this.text_center())\n                .text_color(cx.theme().muted_foreground)\n                .text_sm()\n                .child(\"January-June 2025\"),\n        )\n        .child(div().flex_1().py_4().child(chart))\n        .child(\n            div()\n                .when(center, |this| this.text_center())\n                .font_semibold()\n                .text_sm()\n                .child(\"Trending up by 5.2% this month\"),\n        )\n        .child(\n            div()\n                .when(center, |this| this.text_center())\n                .text_color(cx.theme().muted_foreground)\n                .text_sm()\n                .child(\"Showing total visitors for the last 6 months\"),\n        )\n}\n\nimpl Render for ChartStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let color = cx.theme().chart_3;\n        v_flex()\n            .size_full()\n            .gap_y_4()\n            .bg(cx.theme().background)\n            .child(\n                div().child(chart_container(\n                    \"Area Chart - Stacked\",\n                    AreaChart::new(self.daily_devices.clone())\n                        .x(|d| d.date.clone())\n                        .y(|d| d.desktop)\n                        .stroke(cx.theme().chart_1)\n                        .fill(linear_gradient(\n                            0.,\n                            linear_color_stop(cx.theme().chart_1.opacity(0.4), 1.),\n                            linear_color_stop(cx.theme().background.opacity(0.3), 0.),\n                        ))\n                        .y(|d| d.mobile)\n                        .stroke(cx.theme().chart_2)\n                        .fill(linear_gradient(\n                            0.,\n                            linear_color_stop(cx.theme().chart_2.opacity(0.4), 1.),\n                            linear_color_stop(cx.theme().background.opacity(0.3), 0.),\n                        ))\n                        .tick_margin(8),\n                    false,\n                    cx,\n                )),\n            )\n            .child(\n                h_flex()\n                    .flex_wrap()\n                    .gap_4()\n                    .child(chart_container(\n                        \"Pie Chart\",\n                        PieChart::new(self.monthly_devices.clone())\n                            .value(|d| d.desktop as f32)\n                            .outer_radius(100.)\n                            .color(move |d| d.color(color)),\n                        true,\n                        cx,\n                    ))\n                    .child(chart_container(\n                        \"Pie Chart - Donut\",\n                        PieChart::new(self.monthly_devices.clone())\n                            .value(|d| d.desktop as f32)\n                            .inner_radius(60.)\n                            .outer_radius_fn(|d| 100. - d.index as f32 * 4.)\n                            .color(move |d| d.color(color)),\n                        true,\n                        cx,\n                    ))\n                    .child(chart_container(\n                        \"Pie Chart - Pad Angle\",\n                        PieChart::new(self.monthly_devices.clone())\n                            .value(|d| d.desktop as f32)\n                            .inner_radius(60.)\n                            .outer_radius(100.)\n                            .pad_angle(4. / 100.)\n                            .color(move |d| d.color(color)),\n                        true,\n                        cx,\n                    )),\n            )\n            .child(Divider::horizontal())\n            .child(\n                h_flex()\n                    .flex_wrap()\n                    .gap_4()\n                    .child(chart_container(\n                        \"Bar Chart\",\n                        BarChart::new(self.monthly_devices.clone())\n                            .x(|d| d.month.clone())\n                            .y(|d| d.desktop),\n                        false,\n                        cx,\n                    ))\n                    .child(chart_container(\n                        \"Bar Chart - Mixed\",\n                        BarChart::new(self.monthly_devices.clone())\n                            .x(|d| d.month.clone())\n                            .y(|d| d.desktop)\n                            .fill(move |d| d.color(color)),\n                        false,\n                        cx,\n                    ))\n                    .child({\n                        let data = self.daily_devices.iter().take(8).cloned().collect();\n                        chart_container(\n                            \"Bar Chart - Stacked\",\n                            StackedBarChart::new(data),\n                            false,\n                            cx,\n                        )\n                    })\n                    .child(chart_container(\n                        \"Bar Chart - Label\",\n                        BarChart::new(self.monthly_devices.clone())\n                            .x(|d| d.month.clone())\n                            .y(|d| d.desktop)\n                            .label(|d| d.desktop.to_string()),\n                        false,\n                        cx,\n                    )),\n            )\n            .child(Divider::horizontal())\n            .child(\n                h_flex()\n                    .flex_wrap()\n                    .gap_4()\n                    .child(chart_container(\n                        \"Line Chart\",\n                        LineChart::new(self.monthly_devices.clone())\n                            .x(|d| d.month.clone())\n                            .y(|d| d.desktop),\n                        false,\n                        cx,\n                    ))\n                    .child(chart_container(\n                        \"Line Chart - Linear\",\n                        LineChart::new(self.monthly_devices.clone())\n                            .x(|d| d.month.clone())\n                            .y(|d| d.desktop)\n                            .linear(),\n                        false,\n                        cx,\n                    ))\n                    .child(chart_container(\n                        \"Line Chart - Step After\",\n                        LineChart::new(self.monthly_devices.clone())\n                            .x(|d| d.month.clone())\n                            .y(|d| d.desktop)\n                            .step_after(),\n                        false,\n                        cx,\n                    ))\n                    .child(chart_container(\n                        \"Line Chart - Dots\",\n                        LineChart::new(self.monthly_devices.clone())\n                            .x(|d| d.month.clone())\n                            .y(|d| d.desktop)\n                            .dot()\n                            .stroke(cx.theme().chart_5),\n                        false,\n                        cx,\n                    )),\n            )\n            .child(Divider::horizontal())\n            .child(\n                h_flex()\n                    .flex_wrap()\n                    .gap_4()\n                    .child(chart_container(\n                        \"Area Chart\",\n                        AreaChart::new(self.monthly_devices.clone())\n                            .x(|d| d.month.clone())\n                            .y(|d| d.desktop),\n                        false,\n                        cx,\n                    ))\n                    .child(chart_container(\n                        \"Area Chart - Linear\",\n                        AreaChart::new(self.monthly_devices.clone())\n                            .x(|d| d.month.clone())\n                            .y(|d| d.desktop)\n                            .linear(),\n                        false,\n                        cx,\n                    ))\n                    .child(chart_container(\n                        \"Area Chart - Step After\",\n                        AreaChart::new(self.monthly_devices.clone())\n                            .x(|d| d.month.clone())\n                            .y(|d| d.desktop)\n                            .step_after(),\n                        false,\n                        cx,\n                    ))\n                    .child(chart_container(\n                        \"Area Chart - Linear Gradient\",\n                        AreaChart::new(self.monthly_devices.clone())\n                            .x(|d| d.month.clone())\n                            .y(|d| d.desktop)\n                            .fill(linear_gradient(\n                                0.,\n                                linear_color_stop(cx.theme().chart_1.opacity(0.4), 1.),\n                                linear_color_stop(cx.theme().background.opacity(0.3), 0.),\n                            )),\n                        false,\n                        cx,\n                    )),\n            )\n            .child(Divider::horizontal())\n            .child(\n                h_flex()\n                    .flex_wrap()\n                    .gap_4()\n                    .child(chart_container(\n                        \"Candlestick Chart\",\n                        CandlestickChart::new(self.stock_prices.clone())\n                            .x(|d| d.date.clone())\n                            .open(|d| d.open)\n                            .high(|d| d.high)\n                            .low(|d| d.low)\n                            .close(|d| d.close),\n                        false,\n                        cx,\n                    ))\n                    .child(chart_container(\n                        \"Candlestick Chart - Narrow\",\n                        CandlestickChart::new(self.stock_prices.clone())\n                            .x(|d| d.date.clone())\n                            .open(|d| d.open)\n                            .high(|d| d.high)\n                            .low(|d| d.low)\n                            .close(|d| d.close)\n                            .body_width_ratio(0.5),\n                        false,\n                        cx,\n                    ))\n                    .child(chart_container(\n                        \"Candlestick Chart - Wide\",\n                        CandlestickChart::new(self.stock_prices.clone())\n                            .x(|d| d.date.clone())\n                            .open(|d| d.open)\n                            .high(|d| d.high)\n                            .low(|d| d.low)\n                            .close(|d| d.close)\n                            .body_width_ratio(1.0),\n                        false,\n                        cx,\n                    ))\n                    .child(chart_container(\n                        \"Candlestick Chart - Tick Margin\",\n                        CandlestickChart::new(self.stock_prices.clone())\n                            .x(|d| d.date.clone())\n                            .open(|d| d.open)\n                            .high(|d| d.high)\n                            .low(|d| d.low)\n                            .close(|d| d.close)\n                            .tick_margin(2),\n                        false,\n                        cx,\n                    )),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/chart_story/stacked_bar_chart.rs",
    "content": "// You can draw any chart you want by using the `Plot`.\n\nuse gpui::{App, Bounds, Pixels, TextAlign, Window, px};\nuse gpui_component::{\n    ActiveTheme,\n    plot::{\n        AXIS_GAP, AxisText, Grid, IntoPlot, Plot, PlotAxis,\n        scale::{Scale, ScaleBand, ScaleLinear, ScaleOrdinal},\n        shape::{Bar, Stack, StackSeries},\n    },\n};\n\nuse super::DailyDevice;\n\n#[derive(IntoPlot)]\npub struct StackedBarChart {\n    data: Vec<DailyDevice>,\n    series: Vec<StackSeries<DailyDevice>>,\n}\n\nimpl StackedBarChart {\n    pub fn new(data: Vec<DailyDevice>) -> Self {\n        // 1. Calculate the stacked data\n        let series = Stack::new()\n            .data(data.clone())\n            .keys(vec![\"desktop\", \"mobile\", \"tablet\", \"watch\"])\n            .value(move |d: &DailyDevice, key| match key {\n                \"desktop\" => Some(d.desktop as f32),\n                \"mobile\" => Some(d.mobile as f32),\n                \"tablet\" => Some(d.tablet as f32),\n                \"watch\" => Some(d.watch as f32),\n                _ => None,\n            })\n            .series();\n\n        Self { data, series }\n    }\n}\n\nimpl Plot for StackedBarChart {\n    fn paint(&mut self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App) {\n        let width = bounds.size.width.as_f32();\n        let height = bounds.size.height.as_f32() - AXIS_GAP;\n\n        // 2. Calculate X/Y scales\n        let x = ScaleBand::new(\n            self.data.iter().map(|v| v.date.clone()).collect(),\n            vec![0., width],\n        )\n        .padding_inner(0.4)\n        .padding_outer(0.2);\n        let band_width = x.band_width();\n\n        let max = self\n            .series\n            .iter()\n            .flat_map(|s| s.points.iter().map(|p| p.y1))\n            .fold(0., f32::max) as f64;\n\n        let y = ScaleLinear::new(vec![0., max], vec![height, 10.]);\n\n        // 3. Draw X axis labels\n        let x_label = self.data.iter().filter_map(|d| {\n            x.tick(&d.date.clone()).map(|x_tick| {\n                AxisText::new(\n                    d.date.clone(),\n                    x_tick + band_width / 2.,\n                    cx.theme().muted_foreground,\n                )\n                .align(TextAlign::Center)\n            })\n        });\n        PlotAxis::new()\n            .x(height)\n            .x_label(x_label)\n            .stroke(cx.theme().border)\n            .paint(&bounds, window, cx);\n\n        // 4. Setup color scale\n        let keys = self.series.iter().map(|s| s.key.clone()).collect();\n        let colors = vec![\n            cx.theme().chart_4,\n            cx.theme().chart_3,\n            cx.theme().chart_2,\n            cx.theme().chart_1,\n        ];\n        let ordinal = ScaleOrdinal::new(keys, colors);\n\n        // 5. Draw grid lines\n        Grid::new()\n            .y((0..=3).map(|i| height * i as f32 / 4.0).collect())\n            .stroke(cx.theme().border)\n            .dash_array(&[px(4.), px(2.)])\n            .paint(&bounds, window);\n\n        // 6. Draw stacked bars\n        for series in self.series.iter() {\n            let x = x.clone();\n            let y0 = y.clone();\n            let y1 = y.clone();\n\n            let key = &series.key;\n            let fill = ordinal.map(&key).unwrap_or(cx.theme().chart_4);\n\n            Bar::new()\n                .data(&series.points)\n                .band_width(band_width)\n                .x(move |d| x.tick(&d.data.date.clone()))\n                .y0(move |d| y0.tick(&(d.y0 as f64)).unwrap_or(height))\n                .y1(move |d| y1.tick(&(d.y1 as f64)))\n                .fill(move |_| fill)\n                .paint(&bounds, window, cx);\n        }\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/chart_story.rs",
    "content": "mod chart_story;\nmod stacked_bar_chart;\n\npub use chart_story::*;\npub use stacked_bar_chart::StackedBarChart;\n"
  },
  {
    "path": "crates/story/src/stories/checkbox_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled,\n    Window, div, px,\n};\n\nuse gpui_component::{\n    ActiveTheme, Disableable as _, Sizable, checkbox::Checkbox, h_flex, text::markdown, v_flex,\n};\n\nuse crate::section;\n\npub struct CheckboxStory {\n    focus_handle: gpui::FocusHandle,\n    check1: bool,\n    check2: bool,\n    check3: bool,\n    check4: bool,\n    check5: bool,\n}\n\nimpl super::Story for CheckboxStory {\n    fn title() -> &'static str {\n        \"Checkbox\"\n    }\n\n    fn description() -> &'static str {\n        \"A control that allows the user to toggle between checked and not checked.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl CheckboxStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            check1: false,\n            check2: false,\n            check3: false,\n            check4: false,\n            check5: false,\n        }\n    }\n}\n\nimpl Focusable for CheckboxStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for CheckboxStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .size_full()\n            .justify_start()\n            .gap_3()\n            .child(\n                section(\"Checkbox\")\n                    .child(\n                        Checkbox::new(\"1\")\n                            .checked(self.check1)\n                            .label(\"A normal checkbox\")\n                            .on_click(cx.listener(|this, checked: &bool, _, cx| {\n                                this.check1 = *checked;\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"2\")\n                            .checked(self.check2)\n                            .label(\"Remember me\")\n                            .on_click(cx.listener(|this, checked: &bool, _, cx| {\n                                this.check2 = *checked;\n                                cx.notify();\n                            })),\n                    ),\n            )\n            .child(\n                section(\"Without label\").child(Checkbox::new(\"3\").checked(self.check3).on_click(\n                    cx.listener(|this, checked: &bool, _, _| {\n                        this.check3 = *checked;\n                    }),\n                )),\n            )\n            .child(\n                section(\"Small size\").max_w_md().child(\n                    Checkbox::new(\"4\")\n                        .small()\n                        .checked(self.check4)\n                        .label(\"A small checkbox\")\n                        .on_click(cx.listener(|this, checked: &bool, _, _| {\n                            this.check4 = *checked;\n                        })),\n                ),\n            )\n            .child(\n                section(\"Large size\").max_w_md().child(\n                    Checkbox::new(\"check5\")\n                        .large()\n                        .checked(self.check2)\n                        .label(\"A large checkbox\")\n                        .on_click(cx.listener(|this, checked: &bool, _, _| {\n                            this.check2 = *checked;\n                        })),\n                ),\n            )\n            .child(\n                section(\"Disabled\").max_w_md().child(\n                    h_flex()\n                        .items_center()\n                        .gap_6()\n                        .child(\n                            Checkbox::new(\"check3\")\n                                .label(\"Disabled Checked\")\n                                .checked(true)\n                                .disabled(true),\n                        )\n                        .child(\n                            Checkbox::new(\"check3_1\")\n                                .label(\"Disabled Unchecked\")\n                                .checked(false)\n                                .disabled(true),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Multi-line\").child(\n                    v_flex().gap_4().child(\n                        Checkbox::new(\"multi-line-checkbox\")\n                            .w(px(300.))\n                            .checked(self.check4)\n                            .label(\"A multi-line checkbox.\")\n                            .child(div().text_color(cx.theme().muted_foreground).child(\n                                \"This is a long long label text that \\\n                                should wrap when the text is too long.\",\n                            ))\n                            .on_click(cx.listener(|this, checked: &bool, _, _| {\n                                this.check4 = *checked;\n                            })),\n                    ),\n                ),\n            )\n            .child(\n                section(\"Rich description (Markdown)\").child(\n                    Checkbox::new(\"longlong-markdown-checkbox\")\n                        .w(px(300.))\n                        .checked(self.check5)\n                        .label(\"Label with description (Markdown)\")\n                        .child(\n                            div()\n                                .text_color(cx.theme().muted_foreground)\n                                .child(markdown(\n                                    \"The [long long label](https://github.com) \\\n                            text used **Markdown**, \\\n                            it should wrap when the text is too long.\",\n                                )),\n                        )\n                        .on_click(cx.listener(|this, checked: &bool, _, _| {\n                            this.check5 = *checked;\n                        })),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/clipboard_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, SharedString,\n    Styled, Window,\n};\n\nuse gpui_component::{\n    WindowExt,\n    clipboard::Clipboard,\n    h_flex,\n    input::{Input, InputState},\n    label::Label,\n    v_flex,\n};\n\nuse crate::section;\n\npub struct ClipboardStory {\n    focus_handle: gpui::FocusHandle,\n    url_state: Entity<InputState>,\n    masked: bool,\n}\n\nimpl super::Story for ClipboardStory {\n    fn title() -> &'static str {\n        \"Clipboard\"\n    }\n\n    fn description() -> &'static str {\n        \"A button that helps you copy text or other content to your clipboard.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl ClipboardStory {\n    pub(crate) fn new(window: &mut Window, cx: &mut App) -> Self {\n        let url_state =\n            cx.new(|cx| InputState::new(window, cx).default_value(\"https://github.com\"));\n\n        Self {\n            url_state,\n            focus_handle: cx.focus_handle(),\n            masked: false,\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\nimpl Focusable for ClipboardStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\nimpl Render for ClipboardStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .size_full()\n            .justify_start()\n            .gap_3()\n            .child(\n                section(\"Clipboard\").max_w_md().child(\n                    h_flex()\n                        .gap_2()\n                        .child(Label::new(\"A clipboard button\"))\n                        .child(\n                            Clipboard::new(\"clipboard1\")\n                                .value_fn({\n                                    let view = cx.entity().clone();\n                                    move |_, cx| {\n                                        SharedString::from(format!(\n                                            \"masked :{}\",\n                                            view.read(cx).masked\n                                        ))\n                                    }\n                                })\n                                .on_copied(|value, window, cx| {\n                                    window.push_notification(format!(\"Copied value: {}\", value), cx)\n                                }),\n                        ),\n                ),\n            )\n            .child(\n                section(\"With in an Input\").max_w_md().child(\n                    Input::new(&self.url_state).suffix(\n                        Clipboard::new(\"clipboard2\")\n                            .value_fn({\n                                let state = self.url_state.clone();\n                                move |_, cx| state.read(cx).value()\n                            })\n                            .on_copied(|value, window, cx| {\n                                window.push_notification(format!(\"Copied value: {}\", value), cx)\n                            }),\n                    ),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/collapsible_story.rs",
    "content": "use gpui::div;\nuse gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render,\n    Styled, Window, prelude::FluentBuilder as _,\n};\n\nuse gpui_component::group_box::{GroupBox, GroupBoxVariants as _};\nuse gpui_component::label::Label;\nuse gpui_component::tag::Tag;\nuse gpui_component::{ActiveTheme, IconName, StyledExt, h_flex};\nuse gpui_component::{\n    Sizable,\n    button::{Button, ButtonVariants},\n    collapsible::Collapsible,\n    v_flex,\n};\n\nuse crate::section;\n\npub struct CollapsibleStory {\n    focus_handle: FocusHandle,\n    item1_open: bool,\n    item2_open: bool,\n}\n\nimpl super::Story for CollapsibleStory {\n    fn title() -> &'static str {\n        \"Collapsible\"\n    }\n\n    fn description() -> &'static str {\n        \"An interactive element that expands/collapses.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl CollapsibleStory {\n    pub(crate) fn new(_: &mut Window, cx: &mut App) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            item1_open: false,\n            item2_open: false,\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\n\nimpl Focusable for CollapsibleStory {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for CollapsibleStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let items = [\n            [\"TSLA.US\", \"$423.00\", \"+30.25%\"],\n            [\"NVDA.US\", \"$312.00\", \"+12.12%\"],\n            [\"AAPL.US\", \"$145.00\", \"-8.50%\"],\n        ];\n\n        v_flex()\n            .gap_6()\n            .child(\n                section(\"Expland Paragraphs\").v_flex().child(\n                    Collapsible::new()\n                        .max_w_128()\n                        .gap_1()\n                        .open(self.item1_open)\n                        .child(\n                            \"This is a collapsible component. \\\n            Click the header to expand or collapse the content.\",\n                        )\n                        .content(\n                            \"This is the full content of the Collapsible component. \\\n                        It is only visible when the component is expanded. \\n\\\n                        You can put any content you like here, including text, images, \\\n                        or other UI elements.\n                        \",\n                        )\n                        .child(\n                            h_flex().justify_center().child(\n                                Button::new(\"toggle1\")\n                                    .icon(IconName::ChevronDown)\n                                    .label(\"Show more\")\n                                    .when(self.item1_open, |this| {\n                                        this.icon(IconName::ChevronUp).label(\"Show less\")\n                                    })\n                                    .xsmall()\n                                    .link()\n                                    .on_click({\n                                        cx.listener(move |this, _, _, cx| {\n                                            this.item1_open = !this.item1_open;\n                                            cx.notify();\n                                        })\n                                    }),\n                            ),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Card\").child(\n                    GroupBox::new()\n                        .outline()\n                        .w_80()\n                        .title(\"Collapsible in a Card\")\n                        .child(\n                            Collapsible::new()\n                                .gap_1()\n                                .open(self.item2_open)\n                                .child(\n                                    h_flex()\n                                        .justify_between()\n                                        .child(\n                                            v_flex().child(\"Total Return\").child(\n                                                h_flex()\n                                                    .gap_1()\n                                                    .child(\n                                                        Label::new(\"123.5%\")\n                                                            .text_2xl()\n                                                            .font_semibold(),\n                                                    )\n                                                    .child(\n                                                        Tag::info()\n                                                            .child(\"+4.5%\")\n                                                            .outline()\n                                                            .rounded_full()\n                                                            .small(),\n                                                    ),\n                                            ),\n                                        )\n                                        .child(\n                                            Button::new(\"toggle2\")\n                                                .small()\n                                                .outline()\n                                                .icon(IconName::ChevronDown)\n                                                .label(\"Details\")\n                                                .when(self.item2_open, |this| {\n                                                    this.icon(IconName::ChevronUp)\n                                                })\n                                                .on_click({\n                                                    cx.listener(move |this, _, _, cx| {\n                                                        this.item2_open = !this.item2_open;\n                                                        cx.notify();\n                                                    })\n                                                }),\n                                        ),\n                                )\n                                .content(v_flex().gap_2().children(items.iter().map(|item| {\n                                    let is_up = item[2].starts_with('+');\n\n                                    h_flex().justify_between().child(item[0]).child(\n                                        h_flex()\n                                            .flex_1()\n                                            .justify_end()\n                                            .gap_4()\n                                            .child(div().w_16().justify_end().child(item[1]))\n                                            .child(\n                                                Label::new(item[2])\n                                                    .text_xs()\n                                                    .w_16()\n                                                    .justify_end()\n                                                    .when(is_up, |this| {\n                                                        this.text_color(cx.theme().green)\n                                                    })\n                                                    .when(!is_up, |this| {\n                                                        this.text_color(cx.theme().red)\n                                                    }),\n                                            ),\n                                    )\n                                }))),\n                        ),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/color_picker_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, Hsla, IntoElement, ParentElement as _, Render,\n    Styled as _, Subscription, Window, div, prelude::FluentBuilder as _,\n};\nuse gpui_component::{\n    ActiveTheme as _, Colorize, Sizable,\n    color_picker::{ColorPicker, ColorPickerEvent, ColorPickerState},\n    v_flex,\n};\n\nuse crate::section;\n\npub struct ColorPickerStory {\n    color: Entity<ColorPickerState>,\n    selected_color: Option<Hsla>,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl super::Story for ColorPickerStory {\n    fn title() -> &'static str {\n        \"ColorPicker\"\n    }\n\n    fn description() -> &'static str {\n        \"A color picker to select color.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl ColorPickerStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let color =\n            cx.new(|cx| ColorPickerState::new(window, cx).default_value(cx.theme().primary));\n\n        let _subscriptions = vec![cx.subscribe(&color, |this, _, ev, _| match ev {\n            ColorPickerEvent::Change(color) => {\n                this.selected_color = *color;\n            }\n        })];\n\n        Self {\n            color,\n            selected_color: Some(cx.theme().primary),\n            _subscriptions,\n        }\n    }\n}\n\nimpl Focusable for ColorPickerStory {\n    fn focus_handle(&self, cx: &gpui::App) -> gpui::FocusHandle {\n        self.color.read(cx).focus_handle(cx)\n    }\n}\n\nimpl Render for ColorPickerStory {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex().gap_3().child(\n            section(\"Normal\")\n                .max_w_md()\n                .child(ColorPicker::new(&self.color).small())\n                .when_some(self.selected_color, |this, color| {\n                    this.child(div().w_24().child(color.to_hex()))\n                }),\n        )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/data_table_story.rs",
    "content": "use std::{\n    ops::Range,\n    sync::LazyLock,\n    time::{self, Duration},\n};\n\nuse fake::Fake;\nuse gpui::{\n    Action, AnyElement, App, AppContext, ClickEvent, Context, Div, Entity, Focusable,\n    InteractiveElement, IntoElement, ParentElement, Render, SharedString, Stateful,\n    StatefulInteractiveElement, Styled, Subscription, Task, TextAlign, Window, div,\n    prelude::FluentBuilder as _,\n};\nuse gpui_component::{\n    ActiveTheme as _, Selectable, Sizable as _, Size, StyleSized as _, StyledExt,\n    button::Button,\n    checkbox::Checkbox,\n    h_flex,\n    input::{Input, InputEvent, InputState},\n    label::Label,\n    menu::{DropdownMenu, PopupMenu},\n    spinner::Spinner,\n    table::{Column, ColumnFixed, ColumnSort, DataTable, TableDelegate, TableEvent, TableState},\n    v_flex,\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Action, Clone, PartialEq, Eq, Deserialize)]\n#[action(namespace = data_table_story, no_json)]\nstruct ChangeSize(Size);\n\n#[derive(Action, Clone, PartialEq, Eq, Deserialize)]\n#[action(namespace = data_table_story, no_json)]\nstruct OpenDetail(usize);\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default)]\nstruct Counter {\n    symbol: SharedString,\n    market: SharedString,\n    name: SharedString,\n}\n\nstatic ALL_COUNTERS: LazyLock<Vec<Counter>> =\n    LazyLock::new(|| serde_json::from_str(include_str!(\"../fixtures/counters.json\")).unwrap());\nstatic INCREMENT_ID: LazyLock<std::sync::Mutex<usize>> = LazyLock::new(|| std::sync::Mutex::new(0));\n\nimpl Counter {\n    fn random() -> Self {\n        let len = ALL_COUNTERS.len();\n        let ix = rand::random::<usize>() % len;\n        ALL_COUNTERS[ix].clone()\n    }\n\n    fn symbol_code(&self) -> SharedString {\n        format!(\"{}.{}\", self.symbol, self.market).into()\n    }\n}\n\n#[derive(Clone, Debug, Default)]\nstruct Stock {\n    id: usize,\n    counter: Counter,\n    price: f64,\n    change: f64,\n    change_percent: f64,\n    volume: f64,\n    turnover: f64,\n    market_cap: f64,\n    ttm: f64,\n    five_mins_ranking: f64,\n    th60_days_ranking: f64,\n    year_change_percent: f64,\n    bid: f64,\n    bid_volume: f64,\n    ask: f64,\n    ask_volume: f64,\n    open: f64,\n    prev_close: f64,\n    high: f64,\n    low: f64,\n    turnover_rate: f64,\n    rise_rate: f64,\n    amplitude: f64,\n    pe_status: f64,\n    pb_status: f64,\n    volume_ratio: f64,\n    bid_ask_ratio: f64,\n    latest_pre_close: f64,\n    latest_post_close: f64,\n    pre_market_cap: f64,\n    pre_market_percent: f64,\n    pre_market_change: f64,\n    post_market_cap: f64,\n    post_market_percent: f64,\n    post_market_change: f64,\n    float_cap: f64,\n    shares: i64,\n    shares_float: i64,\n    day_5_ranking: f64,\n    day_10_ranking: f64,\n    day_30_ranking: f64,\n    day_120_ranking: f64,\n    day_250_ranking: f64,\n}\n\nimpl Stock {\n    fn random_update(&mut self) {\n        self.price = (-300.0..999.999).fake::<f64>();\n        self.change = (-0.1..5.0).fake::<f64>();\n        self.change_percent = (-0.1..0.1).fake::<f64>();\n        self.volume = (-300.0..999.999).fake::<f64>();\n        self.turnover = (-300.0..999.999).fake::<f64>();\n        self.market_cap = (-1000.0..9999.999).fake::<f64>();\n        self.ttm = (-1000.0..9999.999).fake::<f64>();\n        self.five_mins_ranking = self.five_mins_ranking * (1.0 + (-0.2..0.2).fake::<f64>());\n        self.bid = self.price * (1.0 + (-0.2..0.2).fake::<f64>());\n        self.bid_volume = (100.0..1000.0).fake::<f64>();\n        self.ask = self.price * (1.0 + (-0.2..0.2).fake::<f64>());\n        self.ask_volume = (100.0..1000.0).fake::<f64>();\n        self.bid_ask_ratio = self.bid / self.ask;\n        self.volume_ratio = self.volume / self.turnover;\n        self.high = self.price * (1.0 + (0.0..1.5).fake::<f64>());\n        self.low = self.price * (1.0 + (-1.5..0.0).fake::<f64>());\n    }\n}\n\nfn random_stocks(size: usize) -> Vec<Stock> {\n    // Incremental ID with size.\n    let start = {\n        let mut id_lock = INCREMENT_ID.lock().unwrap();\n        let start = *id_lock;\n        *id_lock += size + 1;\n        start\n    };\n\n    (start..start + size)\n        .map(|id| Stock {\n            id,\n            counter: Counter::random(),\n            change: (-100.0..100.0).fake(),\n            change_percent: (-0.1..0.1).fake(),\n            volume: (0.0..1000.0).fake(),\n            turnover: (0.0..1000.0).fake(),\n            market_cap: (0.0..1000.0).fake(),\n            ttm: (0.0..1000.0).fake(),\n            five_mins_ranking: (0.0..1000.0).fake(),\n            th60_days_ranking: (0.0..1000.0).fake(),\n            year_change_percent: (-1.0..1.0).fake(),\n            bid: (0.0..1000.0).fake(),\n            bid_volume: (0.0..1000.0).fake(),\n            ask: (0.0..1000.0).fake(),\n            ask_volume: (0.0..1000.0).fake(),\n            open: (0.0..1000.0).fake(),\n            prev_close: (0.0..1000.0).fake(),\n            high: (0.0..1000.0).fake(),\n            low: (0.0..1000.0).fake(),\n            turnover_rate: (0.0..1.0).fake(),\n            rise_rate: (0.0..1.0).fake(),\n            amplitude: (0.0..1000.0).fake(),\n            pe_status: (0.0..1000.0).fake(),\n            pb_status: (0.0..1000.0).fake(),\n            volume_ratio: (0.0..1.0).fake(),\n            bid_ask_ratio: (0.0..1.0).fake(),\n            latest_pre_close: (0.0..1000.0).fake(),\n            latest_post_close: (0.0..1000.0).fake(),\n            pre_market_cap: (0.0..1000.0).fake(),\n            pre_market_percent: (-1.0..1.0).fake(),\n            pre_market_change: (-100.0..100.0).fake(),\n            post_market_cap: (0.0..1000.0).fake(),\n            post_market_percent: (-1.0..1.0).fake(),\n            post_market_change: (-100.0..100.0).fake(),\n            float_cap: (0.0..1000.0).fake(),\n            shares: (100000..9999999).fake(),\n            shares_float: (100000..9999999).fake(),\n            day_5_ranking: (0.0..1000.0).fake(),\n            day_10_ranking: (0.0..1000.0).fake(),\n            day_30_ranking: (0.0..1000.0).fake(),\n            day_120_ranking: (0.0..1000.0).fake(),\n            day_250_ranking: (0.0..1000.0).fake(),\n            ..Default::default()\n        })\n        .collect()\n}\n\nstruct StockTableDelegate {\n    stocks: Vec<Stock>,\n    columns: Vec<Column>,\n    size: Size,\n    loading: bool,\n    lazy_load: bool,\n    full_loading: bool,\n    clicked_row: Option<usize>,\n    eof: bool,\n    visible_rows: Range<usize>,\n    visible_cols: Range<usize>,\n\n    _load_task: Task<()>,\n}\n\nimpl StockTableDelegate {\n    fn new(size: usize) -> Self {\n        Self {\n            size: Size::default(),\n            stocks: random_stocks(size),\n            lazy_load: false,\n            clicked_row: None,\n            columns: vec![\n                Column::new(\"id\", \"ID\")\n                    .width(60.)\n                    .fixed(ColumnFixed::Left)\n                    .resizable(true)\n                    .min_width(40.)\n                    .max_width(100.)\n                    .text_center(),\n                Column::new(\"market\", \"Market\")\n                    .width(60.)\n                    .fixed(ColumnFixed::Left)\n                    .resizable(true)\n                    .min_width(50.),\n                Column::new(\"name\", \"Name\").width(180.).fixed(ColumnFixed::Left).max_width(300.),\n                Column::new(\"symbol\", \"Symbol\").width(100.).fixed(ColumnFixed::Left).sortable(),\n                Column::new(\"price\", \"Price\").sortable().text_right().p_0(),\n                Column::new(\"change\", \"Chg\").sortable().text_right().p_0(),\n                Column::new(\"change_percent\", \"Chg%\").sortable().text_right().p_0(),\n                Column::new(\"volume\", \"Volume\").p_0(),\n                Column::new(\"turnover\", \"Turnover\").p_0(),\n                Column::new(\"market_cap\", \"Market Cap\").p_0(),\n                Column::new(\"ttm\", \"TTM\").p_0(),\n                Column::new(\"five_mins_ranking\", \"5m Ranking\").text_right().p_0(),\n                Column::new(\"th60_days_ranking\", \"60d Ranking\"),\n                Column::new(\"year_change_percent\", \"Year Chg%\"),\n                Column::new(\"bid\", \"Bid\").text_right().p_0(),\n                Column::new(\"bid_volume\", \"Bid Vol\").text_right().p_0(),\n                Column::new(\"ask\", \"Ask\").text_right().p_0(),\n                Column::new(\"ask_volume\", \"Ask Vol\").text_right().p_0(),\n                Column::new(\"open\", \"Open\").text_right().p_0(),\n                Column::new(\"prev_close\", \"Prev Close\").text_right().p_0(),\n                Column::new(\"high\", \"High\").text_right().p_0(),\n                Column::new(\"low\", \"Low\").text_right().p_0(),\n                Column::new(\"turnover_rate\", \"Turnover Rate\"),\n                Column::new(\"rise_rate\", \"Rise Rate\"),\n                Column::new(\"amplitude\", \"Amplitude\"),\n                Column::new(\"pe_status\", \"P/E\"),\n                Column::new(\"pb_status\", \"P/B\"),\n                Column::new(\"volume_ratio\", \"Volume Ratio\").text_right().p_0(),\n                Column::new(\"bid_ask_ratio\", \"Bid Ask Ratio\").text_right().p_0(),\n                Column::new(\"latest_pre_close\", \"Latest Pre Close\"),\n                Column::new(\"latest_post_close\", \"Latest Post Close\"),\n                Column::new(\"pre_market_cap\", \"Pre Mkt Cap\"),\n                Column::new(\"pre_market_percent\", \"Pre Mkt%\"),\n                Column::new(\"pre_market_change\", \"Pre Mkt Chg\"),\n                Column::new(\"post_market_cap\", \"Post Mkt Cap\"),\n                Column::new(\"post_market_percent\", \"Post Mkt%\"),\n                Column::new(\"post_market_change\", \"Post Mkt Chg\"),\n                Column::new(\"float_cap\", \"Float Cap\"),\n                Column::new(\"shares\", \"Shares\"),\n                Column::new(\"shares_float\", \"Float Shares\"),\n                Column::new(\"day_5_ranking\", \"5d Ranking\"),\n                Column::new(\"day_10_ranking\", \"10d Ranking\"),\n                Column::new(\"day_30_ranking\", \"30d Ranking\"),\n                Column::new(\"day_120_ranking\", \"120d Ranking\"),\n                Column::new(\"day_250_ranking\", \"250d Ranking\"),\n            ],\n            loading: false,\n            full_loading: false,\n            eof: false,\n            visible_cols: Range::default(),\n            visible_rows: Range::default(),\n            _load_task: Task::ready(()),\n        }\n    }\n\n    fn update_stocks(&mut self, size: usize) {\n        // Reset incremental ID\n        {\n            let mut id_lock = INCREMENT_ID.lock().unwrap();\n            *id_lock = 0;\n        }\n\n        self.stocks = random_stocks(size);\n        self.eof = size <= 50;\n        self.loading = false;\n        self.full_loading = false;\n    }\n\n    fn render_percent(&self, col: &Column, val: f64, cx: &mut App) -> AnyElement {\n        let right_num = ((val - val.floor()) * 1000.).floor() as i32;\n\n        div()\n            .h_full()\n            .table_cell_size(self.size)\n            .when(col.align == TextAlign::Right, |this| this.h_flex().justify_end())\n            .map(|this| {\n                if right_num % 3 == 0 {\n                    this.text_color(cx.theme().red).bg(cx.theme().red_light.alpha(0.05))\n                } else if right_num % 3 == 1 {\n                    this.text_color(cx.theme().green).bg(cx.theme().green_light.alpha(0.05))\n                } else {\n                    this\n                }\n            })\n            .child(format!(\"{:.2}%\", val * 100.))\n            .into_any_element()\n    }\n\n    fn render_value_cell(&self, col: &Column, val: f64, cx: &mut App) -> AnyElement {\n        let this = div().h_full().table_cell_size(self.size).child(format!(\"{:.3}\", val));\n        // Val is a 0.0 .. n.0\n        // 30% to red, 30% to green, others to default\n        let right_num = ((val - val.floor()) * 1000.).floor() as i32;\n\n        let this = if right_num % 3 == 0 {\n            this.text_color(cx.theme().red).bg(cx.theme().red_light.alpha(0.05))\n        } else if right_num % 3 == 1 {\n            this.text_color(cx.theme().green).bg(cx.theme().green_light.alpha(0.05))\n        } else {\n            this\n        };\n\n        this.when(col.align == TextAlign::Right, |this| this.h_flex().justify_end())\n            .into_any_element()\n    }\n}\n\nimpl TableDelegate for StockTableDelegate {\n    fn columns_count(&self, _: &App) -> usize {\n        self.columns.len()\n    }\n\n    fn rows_count(&self, _: &App) -> usize {\n        self.stocks.len()\n    }\n\n    fn column(&self, col_ix: usize, _cx: &App) -> Column {\n        self.columns[col_ix].clone()\n    }\n\n    fn render_th(\n        &mut self,\n        col_ix: usize,\n        _: &mut Window,\n        _: &mut Context<TableState<Self>>,\n    ) -> impl IntoElement {\n        let col = self.columns.get(col_ix).unwrap();\n\n        div()\n            .child(col.name.clone())\n            .when(col_ix >= 3 && col_ix <= 10, |this| this.table_cell_size(self.size))\n            .when(col.align == TextAlign::Center, |this| this.h_flex().w_full().justify_center())\n            .when(col.align == TextAlign::Right, |this| this.h_flex().w_full().justify_end())\n    }\n\n    fn context_menu(\n        &mut self,\n        row_ix: usize,\n        menu: PopupMenu,\n        _window: &mut Window,\n        _: &mut Context<TableState<Self>>,\n    ) -> PopupMenu {\n        menu.menu(format!(\"Selected Row: {}\", row_ix), Box::new(OpenDetail(row_ix)))\n            .separator()\n            .menu(\"Size Large\", Box::new(ChangeSize(Size::Large)))\n            .menu(\"Size Medium\", Box::new(ChangeSize(Size::Medium)))\n            .menu(\"Size Small\", Box::new(ChangeSize(Size::Small)))\n            .menu(\"Size XSmall\", Box::new(ChangeSize(Size::XSmall)))\n    }\n\n    fn render_tr(\n        &mut self,\n        row_ix: usize,\n        _: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) -> Stateful<Div> {\n        div().id(row_ix).on_click(cx.listener(move |table, ev: &ClickEvent, _window, cx| {\n            println!(\"You have clicked row with secondary: {}\", ev.modifiers().secondary());\n\n            table.delegate_mut().clicked_row = Some(row_ix);\n            cx.notify();\n        }))\n    }\n\n    /// NOTE: Performance metrics\n    ///\n    /// last render 561 cells total: 232.745µs, avg: 414ns\n    /// frame duration: 8.825083ms\n    ///\n    /// This is means render the full table cells takes 232.745µs. Then 232.745µs / 8.82ms = 2.6% of the frame duration.\n    ///\n    /// If we improve the td rendering, we can reduce the time to render the full table cells.\n    fn render_td(\n        &mut self,\n        row_ix: usize,\n        col_ix: usize,\n        _: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) -> impl IntoElement {\n        let stock = self.stocks.get(row_ix).unwrap();\n        let col = self.columns.get(col_ix).unwrap();\n\n        match col.key.as_ref() {\n            \"id\" => div()\n                .child(stock.id.to_string())\n                .when(col.align == TextAlign::Center, |this| this.text_center())\n                .into_any_element(),\n            \"market\" => div()\n                .map(|this| {\n                    if stock.counter.market == \"US\" {\n                        this.text_color(cx.theme().blue)\n                    } else {\n                        this.text_color(cx.theme().magenta)\n                    }\n                })\n                .child(stock.counter.market.clone())\n                .into_any_element(),\n            \"symbol\" => stock.counter.symbol_code().into_any_element(),\n            \"name\" => stock.counter.name.clone().into_any_element(),\n            \"price\" => self.render_value_cell(&col, stock.price, cx),\n            \"change\" => self.render_value_cell(&col, stock.change, cx),\n            \"change_percent\" => self.render_percent(&col, stock.change_percent, cx),\n            \"volume\" => self.render_value_cell(&col, stock.volume, cx),\n            \"turnover\" => self.render_value_cell(&col, stock.turnover, cx),\n            \"market_cap\" => self.render_value_cell(&col, stock.market_cap, cx),\n            \"ttm\" => self.render_value_cell(&col, stock.ttm, cx),\n            \"five_mins_ranking\" => self.render_value_cell(&col, stock.five_mins_ranking, cx),\n            \"th60_days_ranking\" => stock.th60_days_ranking.floor().to_string().into_any_element(),\n            \"year_change_percent\" => self.render_percent(&col, stock.year_change_percent, cx),\n            \"bid\" => self.render_value_cell(&col, stock.bid, cx),\n            \"bid_volume\" => self.render_value_cell(&col, stock.bid_volume, cx),\n            \"ask\" => self.render_value_cell(&col, stock.ask, cx),\n            \"ask_volume\" => self.render_value_cell(&col, stock.ask_volume, cx),\n            \"open\" => self.render_value_cell(&col, stock.open, cx),\n            \"prev_close\" => self.render_value_cell(&col, stock.prev_close, cx),\n            \"high\" => self.render_value_cell(&col, stock.high, cx),\n            \"low\" => self.render_value_cell(&col, stock.low, cx),\n            \"turnover_rate\" => (stock.turnover_rate * 100.0).floor().to_string().into_any_element(),\n            \"rise_rate\" => (stock.rise_rate * 100.0).floor().to_string().into_any_element(),\n            \"amplitude\" => (stock.amplitude * 100.0).floor().to_string().into_any_element(),\n            \"pe_status\" => stock.pe_status.floor().to_string().into_any_element(),\n            \"pb_status\" => stock.pb_status.floor().to_string().into_any_element(),\n            \"volume_ratio\" => self.render_value_cell(&col, stock.volume_ratio, cx),\n            \"bid_ask_ratio\" => self.render_value_cell(&col, stock.bid_ask_ratio, cx),\n            \"latest_pre_close\" => stock.latest_pre_close.floor().to_string().into_any_element(),\n            \"latest_post_close\" => stock.latest_post_close.floor().to_string().into_any_element(),\n            \"pre_market_cap\" => stock.pre_market_cap.floor().to_string().into_any_element(),\n            \"pre_market_percent\" => self.render_percent(&col, stock.pre_market_percent, cx),\n            \"pre_market_change\" => stock.pre_market_change.floor().to_string().into_any_element(),\n            \"post_market_cap\" => stock.post_market_cap.floor().to_string().into_any_element(),\n            \"post_market_percent\" => self.render_percent(&col, stock.post_market_percent, cx),\n            \"post_market_change\" => stock.post_market_change.floor().to_string().into_any_element(),\n            \"float_cap\" => stock.float_cap.floor().to_string().into_any_element(),\n            \"shares\" => stock.shares.to_string().into_any_element(),\n            \"shares_float\" => stock.shares_float.to_string().into_any_element(),\n            \"day_5_ranking\" => stock.day_5_ranking.floor().to_string().into_any_element(),\n            \"day_10_ranking\" => stock.day_10_ranking.floor().to_string().into_any_element(),\n            \"day_30_ranking\" => stock.day_30_ranking.floor().to_string().into_any_element(),\n            \"day_120_ranking\" => stock.day_120_ranking.floor().to_string().into_any_element(),\n            \"day_250_ranking\" => stock.day_250_ranking.floor().to_string().into_any_element(),\n            _ => \"--\".to_string().into_any_element(),\n        }\n    }\n\n    fn move_column(\n        &mut self,\n        col_ix: usize,\n        to_ix: usize,\n        _: &mut Window,\n        _: &mut Context<TableState<Self>>,\n    ) {\n        let col = self.columns.remove(col_ix);\n        self.columns.insert(to_ix, col);\n    }\n\n    fn perform_sort(\n        &mut self,\n        col_ix: usize,\n        sort: ColumnSort,\n        _: &mut Window,\n        _: &mut Context<TableState<Self>>,\n    ) {\n        if let Some(col) = self.columns.get_mut(col_ix) {\n            match col.key.as_ref() {\n                \"id\" => self.stocks.sort_by(|a, b| match sort {\n                    ColumnSort::Descending => b.id.cmp(&a.id),\n                    _ => a.id.cmp(&b.id),\n                }),\n                \"symbol\" => self.stocks.sort_by(|a, b| match sort {\n                    ColumnSort::Descending => b.counter.symbol.cmp(&a.counter.symbol),\n                    _ => a.id.cmp(&b.id),\n                }),\n                \"change\" | \"change_percent\" => self.stocks.sort_by(|a, b| match sort {\n                    ColumnSort::Descending => {\n                        b.change.partial_cmp(&a.change).unwrap_or(std::cmp::Ordering::Equal)\n                    }\n                    _ => a.id.cmp(&b.id),\n                }),\n                _ => {}\n            }\n        }\n    }\n\n    fn loading(&self, _: &App) -> bool {\n        self.full_loading\n    }\n\n    fn has_more(&self, _: &App) -> bool {\n        if !self.lazy_load {\n            return false;\n        }\n        if self.loading {\n            return false;\n        }\n\n        return !self.eof;\n    }\n\n    fn load_more_threshold(&self) -> usize {\n        150\n    }\n\n    fn load_more(&mut self, _: &mut Window, cx: &mut Context<TableState<Self>>) {\n        if !self.lazy_load {\n            return;\n        }\n\n        self.loading = true;\n\n        self._load_task = cx.spawn(async move |view, cx| {\n            // Simulate network request, delay 1s to load data.\n            cx.background_executor().timer(Duration::from_secs(1)).await;\n\n            _ = cx.update(|cx| {\n                let _ = view.update(cx, |view, _| {\n                    view.delegate_mut().stocks.extend(random_stocks(200));\n                    view.delegate_mut().loading = false;\n                    view.delegate_mut().eof = view.delegate().stocks.len() >= 6000;\n                });\n            });\n        });\n    }\n\n    fn visible_rows_changed(\n        &mut self,\n        visible_range: Range<usize>,\n        _: &mut Window,\n        _: &mut Context<TableState<Self>>,\n    ) {\n        self.visible_rows = visible_range;\n    }\n\n    fn visible_columns_changed(\n        &mut self,\n        visible_range: Range<usize>,\n        _: &mut Window,\n        _: &mut Context<TableState<Self>>,\n    ) {\n        self.visible_cols = visible_range;\n    }\n\n    fn cell_text(&self, row_ix: usize, col_ix: usize, _cx: &App) -> String {\n        let Some(stock) = self.stocks.get(row_ix) else {\n            return String::new();\n        };\n        let Some(col) = self.columns.get(col_ix) else {\n            return String::new();\n        };\n\n        match col.key.as_ref() {\n            \"id\" => stock.id.to_string(),\n            \"market\" => stock.counter.market.to_string(),\n            \"symbol\" => stock.counter.symbol_code().to_string(),\n            \"name\" => stock.counter.name.to_string(),\n            \"price\" => format!(\"{:.3}\", stock.price),\n            \"change\" => format!(\"{:.3}\", stock.change),\n            \"change_percent\" => format!(\"{:.2}%\", stock.change_percent * 100.),\n            \"volume\" => format!(\"{:.3}\", stock.volume),\n            \"turnover\" => format!(\"{:.3}\", stock.turnover),\n            \"market_cap\" => format!(\"{:.3}\", stock.market_cap),\n            \"ttm\" => format!(\"{:.3}\", stock.ttm),\n            \"five_mins_ranking\" => format!(\"{:.3}\", stock.five_mins_ranking),\n            \"th60_days_ranking\" => stock.th60_days_ranking.floor().to_string(),\n            \"year_change_percent\" => format!(\"{:.2}%\", stock.year_change_percent * 100.),\n            \"bid\" => format!(\"{:.3}\", stock.bid),\n            \"bid_volume\" => format!(\"{:.3}\", stock.bid_volume),\n            \"ask\" => format!(\"{:.3}\", stock.ask),\n            \"ask_volume\" => format!(\"{:.3}\", stock.ask_volume),\n            \"open\" => format!(\"{:.3}\", stock.open),\n            \"prev_close\" => format!(\"{:.3}\", stock.prev_close),\n            \"high\" => format!(\"{:.3}\", stock.high),\n            \"low\" => format!(\"{:.3}\", stock.low),\n            \"turnover_rate\" => format!(\"{:.0}\", stock.turnover_rate * 100.),\n            \"rise_rate\" => format!(\"{:.0}\", stock.rise_rate * 100.),\n            \"amplitude\" => format!(\"{:.0}\", stock.amplitude * 100.),\n            \"pe_status\" => stock.pe_status.floor().to_string(),\n            \"pb_status\" => stock.pb_status.floor().to_string(),\n            \"volume_ratio\" => format!(\"{:.3}\", stock.volume_ratio),\n            \"bid_ask_ratio\" => format!(\"{:.3}\", stock.bid_ask_ratio),\n            \"latest_pre_close\" => stock.latest_pre_close.floor().to_string(),\n            \"latest_post_close\" => stock.latest_post_close.floor().to_string(),\n            \"pre_market_cap\" => stock.pre_market_cap.floor().to_string(),\n            \"pre_market_percent\" => format!(\"{:.2}%\", stock.pre_market_percent * 100.),\n            \"pre_market_change\" => stock.pre_market_change.floor().to_string(),\n            \"post_market_cap\" => stock.post_market_cap.floor().to_string(),\n            \"post_market_percent\" => format!(\"{:.2}%\", stock.post_market_percent * 100.),\n            \"post_market_change\" => stock.post_market_change.floor().to_string(),\n            \"float_cap\" => stock.float_cap.floor().to_string(),\n            \"shares\" => stock.shares.to_string(),\n            \"shares_float\" => stock.shares_float.to_string(),\n            \"day_5_ranking\" => stock.day_5_ranking.floor().to_string(),\n            \"day_10_ranking\" => stock.day_10_ranking.floor().to_string(),\n            \"day_30_ranking\" => stock.day_30_ranking.floor().to_string(),\n            \"day_120_ranking\" => stock.day_120_ranking.floor().to_string(),\n            \"day_250_ranking\" => stock.day_250_ranking.floor().to_string(),\n            _ => String::new(),\n        }\n    }\n}\n\npub struct DataTableStory {\n    table: Entity<TableState<StockTableDelegate>>,\n    num_stocks_input: Entity<InputState>,\n    stripe: bool,\n    refresh_data: bool,\n    size: Size,\n\n    _subscriptions: Vec<Subscription>,\n    _load_task: Task<()>,\n}\n\nimpl super::Story for DataTableStory {\n    fn title() -> &'static str {\n        \"DataTable\"\n    }\n\n    fn description() -> &'static str {\n        \"A complex data table with selection, sorting, column moving, and loading more.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n\n    fn closable() -> bool {\n        false\n    }\n}\n\nimpl Focusable for DataTableStory {\n    fn focus_handle(&self, cx: &gpui::App) -> gpui::FocusHandle {\n        self.table.focus_handle(cx)\n    }\n}\n\nimpl DataTableStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        // Create the number input field with validation for positive integers\n        let num_stocks_input = cx.new(|cx| {\n            let mut input = InputState::new(window, cx)\n                .placeholder(\"Enter number of Stocks to display\")\n                .validate(|s, _| s.parse::<usize>().is_ok());\n            input.set_value(\"5000\", window, cx);\n            input\n        });\n\n        let delegate = StockTableDelegate::new(5000);\n        let table = cx.new(|cx| TableState::new(delegate, window, cx));\n\n        let _subscriptions = vec![\n            cx.subscribe_in(&table, window, Self::on_table_event),\n            cx.subscribe_in(&num_stocks_input, window, Self::on_num_stocks_input_change),\n            // Spawn a background to random refresh the list\n        ];\n\n        let _load_task = cx.spawn(async move |this, cx| {\n            loop {\n                cx.background_executor().timer(time::Duration::from_millis(33)).await;\n\n                this.update(cx, |this, cx| {\n                    if !this.refresh_data {\n                        return;\n                    }\n\n                    this.table.update(cx, |table, _| {\n                        table.delegate_mut().stocks.iter_mut().enumerate().for_each(\n                            |(i, stock)| {\n                                let n = (3..10).fake::<usize>();\n                                // update 30% of the stocks\n                                if i % n == 0 {\n                                    stock.random_update();\n                                }\n                            },\n                        );\n                    });\n                    cx.notify();\n                })\n                .ok();\n            }\n        });\n\n        Self {\n            table,\n            num_stocks_input,\n            stripe: false,\n            refresh_data: false,\n            size: Size::default(),\n            _subscriptions,\n            _load_task,\n        }\n    }\n\n    // Event handler for changes in the number input field\n    fn on_num_stocks_input_change(\n        &mut self,\n        _: &Entity<InputState>,\n        event: &InputEvent,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        match event {\n            // Update when the user presses Enter or the input loses focus\n            InputEvent::PressEnter { .. } | InputEvent::Blur => {\n                let text = self.num_stocks_input.read(cx).value().to_string();\n                if let Ok(total_count) = text.parse::<usize>() {\n                    if total_count == self.table.read(cx).delegate().stocks.len() {\n                        return;\n                    }\n\n                    self.table.update(cx, |table, _| {\n                        table.delegate_mut().update_stocks(total_count);\n                    });\n                    cx.notify();\n                }\n            }\n            _ => {}\n        }\n    }\n\n    fn toggle_loop_selection(&mut self, checked: &bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.table.update(cx, |table, cx| {\n            table.loop_selection = *checked;\n            cx.notify();\n        });\n    }\n\n    fn toggle_col_resize(&mut self, checked: &bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.table.update(cx, |table, cx| {\n            table.col_resizable = *checked;\n            cx.notify();\n        });\n    }\n\n    fn toggle_col_order(&mut self, checked: &bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.table.update(cx, |table, cx| {\n            table.col_movable = *checked;\n            cx.notify();\n        });\n    }\n\n    fn toggle_col_sort(&mut self, checked: &bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.table.update(cx, |table, cx| {\n            table.sortable = *checked;\n            cx.notify();\n        });\n    }\n\n    fn toggle_col_fixed(&mut self, checked: &bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.table.update(cx, |table, cx| {\n            table.col_fixed = *checked;\n            cx.notify();\n        });\n    }\n\n    fn toggle_col_selection(&mut self, checked: &bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.table.update(cx, |table, cx| {\n            table.col_selectable = *checked;\n            cx.notify();\n        });\n    }\n\n    fn toggle_row_selection(&mut self, checked: &bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.table.update(cx, |table, cx| {\n            table.row_selectable = *checked;\n            cx.notify();\n        });\n    }\n\n    fn toggle_cell_selection(&mut self, checked: &bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.table.update(cx, |table, cx| {\n            table.cell_selectable = *checked;\n            cx.notify();\n        });\n    }\n\n    fn toggle_stripe(&mut self, checked: &bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.stripe = *checked;\n        cx.notify();\n    }\n\n    fn on_change_size(&mut self, a: &ChangeSize, _: &mut Window, cx: &mut Context<Self>) {\n        self.size = a.0;\n        cx.notify();\n    }\n\n    fn toggle_refresh_data(&mut self, checked: &bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.refresh_data = *checked;\n        cx.notify();\n    }\n\n    fn on_table_event(\n        &mut self,\n        _: &Entity<TableState<StockTableDelegate>>,\n        event: &TableEvent,\n        _window: &mut Window,\n        _cx: &mut Context<Self>,\n    ) {\n        match event {\n            TableEvent::ColumnWidthsChanged(col_widths) => {\n                println!(\"Column widths changed: {:?}\", col_widths)\n            }\n            TableEvent::SelectColumn(ix) => println!(\"Select col: {}\", ix),\n            TableEvent::SelectCell(row_ix, col_ix) => {\n                println!(\"Select cell: row={}, col={}\", row_ix, col_ix)\n            }\n            TableEvent::DoubleClickedCell(row_ix, col_ix) => {\n                println!(\"Double clicked cell: row={}, col={}\", row_ix, col_ix)\n            }\n            TableEvent::DoubleClickedRow(ix) => println!(\"Double clicked row: {}\", ix),\n            TableEvent::SelectRow(ix) => println!(\"Select row: {}\", ix),\n            TableEvent::MoveColumn(origin_idx, target_idx) => {\n                println!(\"Move col index: {} -> {}\", origin_idx, target_idx);\n            }\n            TableEvent::RightClickedRow(ix) => println!(\"Right clicked row: {:?}\", ix),\n            TableEvent::RightClickedCell(row_ix, col_ix) => {\n                println!(\"Right clicked cell: row={}, col={}\", row_ix, col_ix)\n            }\n            TableEvent::ClearSelection => {\n                println!(\"Selection cleared\");\n            }\n        }\n    }\n\n    fn dump_csv(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {\n        match self.write_csv(cx) {\n            Ok(csv_content) => {\n                let Some(path) = dirs::download_dir() else {\n                    eprintln!(\"Failed to get download directory\");\n                    return;\n                };\n                let receiver = cx.prompt_for_new_path(&path, Some(\"export.csv\"));\n                cx.spawn_in(window, async move |_, _| {\n                    if let Some(path) = receiver.await.ok().into_iter().flatten().flatten().next() {\n                        match std::fs::write(&path, csv_content) {\n                            Ok(_) => {\n                                println!(\"CSV exported successfully to: {:?}\", path);\n                            }\n                            Err(e) => {\n                                eprintln!(\"Failed to save CSV file: {}\", e);\n                            }\n                        }\n                    } else {\n                        println!(\"CSV export cancelled by user\");\n                    };\n                })\n                .detach();\n            }\n            Err(e) => {\n                eprintln!(\"Failed to export CSV: {}\", e);\n            }\n        }\n    }\n\n    fn write_csv(&mut self, cx: &mut Context<Self>) -> anyhow::Result<String> {\n        let (headers, rows) = self.table.update(cx, |table, cx| table.dump(cx));\n\n        // Convert to CSV format using rust-csv\n        let mut wtr = csv::Writer::from_writer(vec![]);\n\n        // Write header\n        wtr.write_record(&headers)?;\n\n        // Write data rows\n        for row in rows {\n            wtr.write_record(&row)?;\n        }\n\n        // Flush and get the CSV data\n        wtr.flush()?;\n        let data = wtr.into_inner().map_err(csv::IntoInnerError::into_error)?;\n        let csv_content = String::from_utf8(data)?;\n\n        Ok(csv_content)\n    }\n}\n\nimpl Render for DataTableStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {\n        let table = &self.table.read(cx);\n        let delegate = table.delegate();\n        let rows_count = delegate.rows_count(cx);\n        let size = self.size;\n\n        v_flex()\n            .on_action(cx.listener(Self::on_change_size))\n            .size_full()\n            .text_sm()\n            .gap_4()\n            .child(\n                h_flex()\n                    .items_center()\n                    .gap_3()\n                    .flex_wrap()\n                    .child(\n                        Checkbox::new(\"loop-selection\")\n                            .label(\"Loop Selection\")\n                            .selected(table.loop_selection)\n                            .on_click(cx.listener(Self::toggle_loop_selection)),\n                    )\n                    .child(\n                        Checkbox::new(\"col-resize\")\n                            .label(\"Column Resize\")\n                            .selected(table.col_resizable)\n                            .on_click(cx.listener(Self::toggle_col_resize)),\n                    )\n                    .child(\n                        Checkbox::new(\"col-order\")\n                            .label(\"Column Order\")\n                            .selected(table.col_movable)\n                            .on_click(cx.listener(Self::toggle_col_order)),\n                    )\n                    .child(\n                        Checkbox::new(\"col-sort\")\n                            .label(\"Sortable\")\n                            .selected(table.sortable)\n                            .on_click(cx.listener(Self::toggle_col_sort)),\n                    )\n                    .child(\n                        Checkbox::new(\"col-selection\")\n                            .label(\"Column Selectable\")\n                            .selected(table.col_selectable)\n                            .on_click(cx.listener(Self::toggle_col_selection)),\n                    )\n                    .child(\n                        Checkbox::new(\"row-selection\")\n                            .label(\"Row Selectable\")\n                            .selected(table.row_selectable)\n                            .on_click(cx.listener(Self::toggle_row_selection)),\n                    )\n                    .child(\n                        Checkbox::new(\"cell-selection\")\n                            .label(\"Cell Selectable\")\n                            .selected(table.cell_selectable)\n                            .on_click(cx.listener(Self::toggle_cell_selection)),\n                    )\n                    .child(\n                        Checkbox::new(\"fixed\")\n                            .label(\"Column Fixed\")\n                            .selected(table.col_fixed)\n                            .on_click(cx.listener(Self::toggle_col_fixed)),\n                    )\n                    .child(\n                        Checkbox::new(\"stripe\")\n                            .label(\"Stripe\")\n                            .selected(self.stripe)\n                            .on_click(cx.listener(Self::toggle_stripe)),\n                    )\n                    .child(\n                        Checkbox::new(\"loading\")\n                            .label(\"Loading\")\n                            .checked(self.table.read(cx).delegate().full_loading)\n                            .on_click(cx.listener(|this, check: &bool, _, cx| {\n                                this.table.update(cx, |this, cx| {\n                                    this.delegate_mut().full_loading = *check;\n                                    cx.notify();\n                                })\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"refresh-data\")\n                            .label(\"Refresh Data\")\n                            .selected(self.refresh_data)\n                            .on_click(cx.listener(Self::toggle_refresh_data)),\n                    ),\n            )\n            .child(\n                h_flex()\n                    .gap_2()\n                    .child(\n                        Button::new(\"size\")\n                            .outline()\n                            .small()\n                            .label(format!(\"size: {:?}\", self.size))\n                            .dropdown_menu(move |menu, _, _| {\n                                menu.menu_with_check(\n                                    \"Large\",\n                                    size == Size::Large,\n                                    Box::new(ChangeSize(Size::Large)),\n                                )\n                                .menu_with_check(\n                                    \"Medium\",\n                                    size == Size::Medium,\n                                    Box::new(ChangeSize(Size::Medium)),\n                                )\n                                .menu_with_check(\n                                    \"Small\",\n                                    size == Size::Small,\n                                    Box::new(ChangeSize(Size::Small)),\n                                )\n                                .menu_with_check(\n                                    \"XSmall\",\n                                    size == Size::XSmall,\n                                    Box::new(ChangeSize(Size::XSmall)),\n                                )\n                            }),\n                    )\n                    .child(\n                        Button::new(\"scroll-top\")\n                            .outline()\n                            .small()\n                            .child(\"Scroll to Top\")\n                            .on_click(cx.listener(|this, _, _, cx| {\n                                this.table.update(cx, |table, cx| {\n                                    table.scroll_to_row(0, cx);\n                                })\n                            })),\n                    )\n                    .child(\n                        Button::new(\"scroll-bottom\")\n                            .outline()\n                            .small()\n                            .child(\"Scroll to Bottom\")\n                            .on_click(cx.listener(|this, _, _, cx| {\n                                this.table.update(cx, |table, cx| {\n                                    table.scroll_to_row(table.delegate().rows_count(cx) - 1, cx);\n                                })\n                            })),\n                    )\n                    .child(\n                        Button::new(\"dump-csv\")\n                            .outline()\n                            .small()\n                            .label(\"Dump CSV\")\n                            .on_click(cx.listener(Self::dump_csv)),\n                    )\n                    .child(\n                        Button::new(\"select-cell-5-3\")\n                            .outline()\n                            .small()\n                            .child(\"Select Cell (5, 3)\")\n                            .on_click(cx.listener(|this, _, _, cx| {\n                                this.table.update(cx, |table, cx| {\n                                    table.set_selected_cell(5, 3, cx);\n                                })\n                            })),\n                    )\n                    .child(\n                        Button::new(\"select-cell-10-7\")\n                            .outline()\n                            .small()\n                            .child(\"Select Cell (10, 7)\")\n                            .on_click(cx.listener(|this, _, _, cx| {\n                                this.table.update(cx, |table, cx| {\n                                    table.set_selected_cell(10, 7, cx);\n                                })\n                            })),\n                    )\n                    .child(\n                        Button::new(\"clear-selection\")\n                            .outline()\n                            .small()\n                            .child(\"Clear Selection\")\n                            .on_click(cx.listener(|this, _, _, cx| {\n                                this.table.update(cx, |table, cx| {\n                                    table.clear_selection(cx);\n                                })\n                            })),\n                    ),\n            )\n            .child(\n                h_flex().items_center().gap_2().child(\n                    h_flex()\n                        .w_full()\n                        .items_center()\n                        .justify_between()\n                        .gap_2()\n                        .child(\n                            h_flex()\n                                .gap_2()\n                                .flex_1()\n                                .child(Label::new(\"Number of Stocks:\"))\n                                .child(\n                                    h_flex()\n                                        .min_w_32()\n                                        .child(Input::new(&self.num_stocks_input).small())\n                                        .into_any_element(),\n                                )\n                                .when(delegate.loading, |this| {\n                                    this.child(\n                                        h_flex().gap_1().child(Spinner::new()).child(\"Loading...\"),\n                                    )\n                                })\n                                .child(\n                                    Checkbox::new(\"lazy-load\")\n                                        .label(\"Lazy Load\")\n                                        .checked(delegate.lazy_load)\n                                        .on_click(cx.listener(|this, check: &bool, _, cx| {\n                                            this.table.update(cx, |table, cx| {\n                                                table.delegate_mut().lazy_load = *check;\n                                                cx.notify();\n                                            })\n                                        })),\n                                ),\n                        )\n                        .child(\n                            h_flex()\n                                .gap_2()\n                                .child(format!(\"Total Rows: {}\", rows_count))\n                                .child(format!(\"Visible Rows: {:?}\", delegate.visible_rows))\n                                .child(format!(\"Visible Cols: {:?}\", delegate.visible_cols))\n                                .when_some(table.selected_cell(), |this, (row, col)| {\n                                    this.child(format!(\"Selected Cell: ({}, {})\", row, col))\n                                })\n                                .when(delegate.eof, |this| this.child(\"All data loaded.\")),\n                        ),\n                ),\n            )\n            .child(DataTable::new(&self.table).with_size(self.size).stripe(self.stripe))\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/date_picker_story.rs",
    "content": "use chrono::{Datelike, Days, Duration, Utc};\nuse gpui::{\n    App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement as _, Render,\n    Styled as _, Subscription, Window, div, px,\n};\nuse gpui_component::{\n    ActiveTheme as _, Sizable as _, calendar,\n    date_picker::{DatePicker, DatePickerEvent, DatePickerState, DateRangePreset},\n    v_flex,\n};\n\nuse crate::section;\n\npub struct DatePickerStory {\n    date_picker: Entity<DatePickerState>,\n    date_picker_small: Entity<DatePickerState>,\n    date_picker_large: Entity<DatePickerState>,\n    data_picker_custom: Entity<DatePickerState>,\n    date_picker_value: Option<String>,\n    date_range_picker: Entity<DatePickerState>,\n    default_range_mode_picker: Entity<DatePickerState>,\n    without_appearance_picker: Entity<DatePickerState>,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl super::Story for DatePickerStory {\n    fn title() -> &'static str {\n        \"DatePicker\"\n    }\n\n    fn description() -> &'static str {\n        \"A date picker to select a date or date range.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl DatePickerStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let now = chrono::Local::now().naive_local().date();\n        let date_picker = cx.new(|cx| {\n            let mut picker = DatePickerState::new(window, cx).disabled_matcher(vec![0, 6]);\n            picker.set_date(now, window, cx);\n            picker\n        });\n        let date_picker_large = cx.new(|cx| {\n            let mut picker = DatePickerState::new(window, cx)\n                .date_format(\"%Y-%m-%d\")\n                .disabled_matcher(calendar::Matcher::range(\n                    Some(now),\n                    now.checked_add_days(Days::new(7)),\n                ));\n            picker.set_date(\n                now.checked_sub_days(Days::new(1)).unwrap_or_default(),\n                window,\n                cx,\n            );\n            picker\n        });\n        let date_picker_small = cx.new(|cx| {\n            let mut picker = DatePickerState::new(window, cx).disabled_matcher(\n                calendar::Matcher::interval(Some(now), now.checked_add_days(Days::new(5))),\n            );\n            picker.set_date(now, window, cx);\n            picker\n        });\n        let data_picker_custom = cx.new(|cx| {\n            let mut picker = DatePickerState::new(window, cx)\n                .disabled_matcher(calendar::Matcher::custom(|date| date.day0() < 5));\n            picker.set_date(now, window, cx);\n            picker\n        });\n        let date_range_picker = cx.new(|cx| {\n            let mut picker = DatePickerState::new(window, cx);\n            picker.set_date(\n                (now, now.checked_add_days(Days::new(4)).unwrap()),\n                window,\n                cx,\n            );\n            picker\n        });\n\n        let default_range_mode_picker = cx.new(|cx| DatePickerState::range(window, cx));\n\n        let without_appearance_picker = cx.new(|cx| DatePickerState::new(window, cx));\n\n        let _subscriptions = vec![\n            cx.subscribe(&date_picker, |this, _, ev, _| match ev {\n                DatePickerEvent::Change(date) => {\n                    this.date_picker_value = date.format(\"%Y-%m-%d\").map(|s| s.to_string());\n                }\n            }),\n            cx.subscribe(&date_range_picker, |this, _, ev, _| match ev {\n                DatePickerEvent::Change(date) => {\n                    this.date_picker_value = date.format(\"%Y-%m-%d\").map(|s| s.to_string());\n                }\n            }),\n            cx.subscribe(&default_range_mode_picker, |this, _, ev, _| match ev {\n                DatePickerEvent::Change(date) => {\n                    this.date_picker_value = date.format(\"%Y-%m-%d\").map(|s| s.to_string());\n                }\n            }),\n        ];\n\n        Self {\n            date_picker,\n            date_picker_large,\n            date_picker_small,\n            data_picker_custom,\n            date_range_picker,\n            default_range_mode_picker,\n            without_appearance_picker,\n            date_picker_value: None,\n            _subscriptions,\n        }\n    }\n}\n\nimpl Focusable for DatePickerStory {\n    fn focus_handle(&self, cx: &gpui::App) -> gpui::FocusHandle {\n        self.date_picker.focus_handle(cx)\n    }\n}\n\nimpl Render for DatePickerStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let presets = vec![\n            DateRangePreset::single(\n                \"Yesterday\",\n                (Utc::now() - Duration::days(1)).naive_local().date(),\n            ),\n            DateRangePreset::single(\n                \"Last Week\",\n                (Utc::now() - Duration::weeks(1)).naive_local().date(),\n            ),\n            DateRangePreset::single(\n                \"Last Month\",\n                (Utc::now() - Duration::days(30)).naive_local().date(),\n            ),\n        ];\n        let range_presets = vec![\n            DateRangePreset::range(\n                \"Last 7 Days\",\n                (Utc::now() - Duration::days(7)).naive_local().date(),\n                Utc::now().naive_local().date(),\n            ),\n            DateRangePreset::range(\n                \"Last 14 Days\",\n                (Utc::now() - Duration::days(14)).naive_local().date(),\n                Utc::now().naive_local().date(),\n            ),\n            DateRangePreset::range(\n                \"Last 30 Days\",\n                (Utc::now() - Duration::days(30)).naive_local().date(),\n                Utc::now().naive_local().date(),\n            ),\n            DateRangePreset::range(\n                \"Last 90 Days\",\n                (Utc::now() - Duration::days(90)).naive_local().date(),\n                Utc::now().naive_local().date(),\n            ),\n        ];\n\n        v_flex()\n            .gap_3()\n            .child(\n                section(\"Normal\").max_w_128().child(\n                    DatePicker::new(&self.date_picker)\n                        .cleanable(true)\n                        .presets(presets),\n                ),\n            )\n            .child(\n                section(\"Small with 180px width\")\n                    .max_w_128()\n                    .child(DatePicker::new(&self.date_picker_small).small().w(px(180.))),\n            )\n            .child(\n                section(\"Large\")\n                    .max_w_128()\n                    .child(DatePicker::new(&self.date_picker_large).large().w(px(300.))),\n            )\n            .child(\n                section(\"Custom (First 5 days of each month disabled)\")\n                    .max_w_128()\n                    .child(DatePicker::new(&self.data_picker_custom)),\n            )\n            .child(\n                section(\"Date Range\").max_w_128().child(\n                    DatePicker::new(&self.date_range_picker)\n                        .number_of_months(2)\n                        .cleanable(true)\n                        .presets(range_presets.clone()),\n                ),\n            )\n            .child(\n                section(\"Default Range Mode\").max_w_128().child(\n                    DatePicker::new(&self.default_range_mode_picker)\n                        .placeholder(\"Range mode picker\")\n                        .cleanable(true)\n                        .presets(range_presets.clone()),\n                ),\n            )\n            .child(\n                section(\"Date Picker Value\").max_w_128().child(\n                    format!(\"Date picker value: {:?}\", self.date_picker_value).into_element(),\n                ),\n            )\n            .child(\n                section(\"Without Appearance\").max_w_128().child(\n                    div().w_full().bg(cx.theme().secondary).child(\n                        DatePicker::new(&self.without_appearance_picker)\n                            .appearance(false)\n                            .placeholder(\"Without appearance\"),\n                    ),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/description_list_story.rs",
    "content": "use gpui::*;\nuse gpui::{\n    Action, App, AppContext, Axis, Context, Entity, FocusHandle, Focusable, IntoElement,\n    ParentElement, Render, Styled, Window,\n};\nuse gpui_component::{AxisExt, h_flex, menu::DropdownMenu as _};\nuse gpui_component::{\n    Sizable as _, Size,\n    button::Button,\n    checkbox::Checkbox,\n    description_list::{DescriptionItem, DescriptionList},\n    dock::PanelControl,\n    text::TextView,\n    v_flex,\n};\nuse serde::Deserialize;\n\n#[derive(Action, Clone, PartialEq, Eq, Deserialize)]\n#[action(namespace = description_list_story, no_json)]\nstruct ChangeSize(Size);\n\npub struct DescriptionListStory {\n    focus_handle: FocusHandle,\n    layout: Axis,\n    bordered: bool,\n    size: Size,\n    items: Vec<(&'static str, &'static str, usize)>,\n}\n\nimpl DescriptionListStory {\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        let items = vec![\n            (\"Name\", \"GPUI Component\", 1),\n            (\n                \"Description\",\n                \"UI components for building fantastic desktop application by using [GPUI](https://gpui.rs).\\\n                \\n\\n \\\n                Contains a lot of useful UI components, such as **Button**, **Input**, **Table**, **List**, **Select**, **DatePicker** ... \\\n                \\n\\n \\\n                You can easily create your native desktop application by using GPUI Component.\n                \",\n                3,\n            ),\n            (\"Version\", \"0.1.0\", 1),\n            (\"License\", \"Apache-2.0\", 1),\n            (\"Author\", \"Longbridge\", 1),\n            (\"--\", \"--\", 1),\n            (\n                \"Repository\",\n                \"https://github.com/longbridge/gpui-component\",\n                2,\n            ),\n            (\n                \"Category\",\n                \"UI, Desktop, Framework\",\n                1,\n            ),\n            (\n                \"This is a long label for Platform\",\n                \"macOS, Windows, Linux\",\n                1,\n            ),\n        ];\n\n        Self {\n            items,\n            bordered: true,\n            size: Size::default(),\n            layout: Axis::Horizontal,\n            focus_handle: cx.focus_handle(),\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn set_layout(&mut self, layout: Axis, cx: &mut Context<Self>) {\n        self.layout = layout;\n        cx.notify();\n    }\n\n    fn set_bordered(&mut self, bordered: bool, cx: &mut Context<Self>) {\n        self.bordered = bordered;\n        cx.notify();\n    }\n\n    fn on_change_size(&mut self, a: &ChangeSize, _: &mut Window, cx: &mut Context<Self>) {\n        self.size = a.0;\n        cx.notify();\n    }\n}\n\nimpl super::Story for DescriptionListStory {\n    fn title() -> &'static str {\n        \"DescriptionList\"\n    }\n\n    fn description() -> &'static str {\n        \"Use to display details with a tidy layout.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n\n    fn zoomable() -> Option<PanelControl> {\n        None\n    }\n}\n\nimpl Focusable for DescriptionListStory {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for DescriptionListStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .id(\"example\")\n            .on_action(cx.listener(Self::on_change_size))\n            .p_4()\n            .size_full()\n            .gap_2()\n            .child(\n                h_flex()\n                    .gap_3()\n                    .child(\n                        Checkbox::new(\"layout\")\n                            .checked(self.layout.is_vertical())\n                            .label(\"Vertical Layout\")\n                            .on_click(cx.listener(|this, checked: &bool, _, cx| {\n                                let new_layout = if *checked {\n                                    Axis::Vertical\n                                } else {\n                                    Axis::Horizontal\n                                };\n                                this.set_layout(new_layout, cx);\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"bordered\")\n                            .checked(self.bordered)\n                            .label(\"Bordered\")\n                            .on_click(cx.listener(|this, checked: &bool, _, cx| {\n                                this.set_bordered(*checked, cx);\n                            })),\n                    )\n                    .child(\n                        Button::new(\"size\")\n                            .small()\n                            .outline()\n                            .label(format!(\"size: {:?}\", self.size))\n                            .dropdown_menu({\n                                let size = self.size;\n                                move |menu, _, _| {\n                                    menu.menu_with_check(\n                                        \"Large\",\n                                        size == Size::Large,\n                                        Box::new(ChangeSize(Size::Large)),\n                                    )\n                                    .menu_with_check(\n                                        \"Medium\",\n                                        size == Size::Medium,\n                                        Box::new(ChangeSize(Size::Medium)),\n                                    )\n                                    .menu_with_check(\n                                        \"Small\",\n                                        size == Size::Small,\n                                        Box::new(ChangeSize(Size::Small)),\n                                    )\n                                }\n                            }),\n                    ),\n            )\n            .child(\n                DescriptionList::new()\n                    .columns(3)\n                    .layout(self.layout)\n                    .bordered(self.bordered)\n                    .with_size(self.size)\n                    .children(self.items.clone().into_iter().enumerate().map(\n                        |(ix, (label, value, span))| {\n                            if label == \"--\" {\n                                return DescriptionItem::Divider;\n                            }\n\n                            DescriptionItem::new(label)\n                                .value(TextView::markdown(ix, value).into_any_element())\n                                .span(span)\n                        },\n                    )),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/dialog_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, InteractiveElement as _, IntoElement,\n    ParentElement, Render, SharedString, Styled, Window, div, px,\n};\n\nuse gpui_component::{\n    ActiveTheme, Icon, IconName, WindowExt as _,\n    button::{Button, ButtonVariants as _},\n    checkbox::Checkbox,\n    date_picker::{DatePicker, DatePickerState},\n    dialog::{\n        Dialog, DialogAction, DialogClose, DialogDescription, DialogFooter, DialogHeader,\n        DialogTitle,\n    },\n    h_flex,\n    input::{Input, InputState},\n    select::{Select, SelectState},\n    table::{Column, DataTable, TableDelegate, TableState},\n    text::markdown,\n    v_flex,\n};\n\nuse crate::{TestAction, section};\n\npub struct DialogStory {\n    focus_handle: FocusHandle,\n    selected_value: Option<SharedString>,\n    input1: Entity<InputState>,\n    input2: Entity<InputState>,\n    date: Entity<DatePickerState>,\n    select: Entity<SelectState<Vec<String>>>,\n    table: Entity<TableState<MyTable>>,\n    dialog_overlay: bool,\n    close_button: bool,\n    keyboard: bool,\n    overlay_closable: bool,\n}\n\nstruct MyTable {\n    columns: Vec<Column>,\n}\nimpl MyTable {\n    fn new(_: &mut App) -> Self {\n        let columns = vec![\n            Column::new(\"id\", \"ID\").width(px(50.)),\n            Column::new(\"name\", \"Name\").width(px(150.)),\n            Column::new(\"email\", \"Email\").width(px(250.)),\n            Column::new(\"role\", \"Role\").width(px(150.)),\n            Column::new(\"status\", \"Status\").width(px(100.)),\n        ];\n\n        Self { columns }\n    }\n}\nimpl TableDelegate for MyTable {\n    fn columns_count(&self, _: &App) -> usize {\n        5\n    }\n\n    fn rows_count(&self, _: &App) -> usize {\n        200\n    }\n\n    fn column(&self, col_ix: usize, _: &App) -> Column {\n        self.columns[col_ix].clone()\n    }\n\n    fn render_td(\n        &mut self,\n        row_ix: usize,\n        col_ix: usize,\n        _: &mut Window,\n        _: &mut Context<TableState<Self>>,\n    ) -> impl IntoElement {\n        match col_ix {\n            0 => format!(\"{}\", row_ix).into_any_element(),\n            1 => format!(\"User {}\", row_ix).into_any_element(),\n            2 => format!(\"user-{}@mail.com\", row_ix).into_any_element(),\n            3 => \"User\".into_any_element(),\n            4 => \"Active\".into_any_element(),\n            _ => panic!(\"Invalid column index\"),\n        }\n    }\n}\n\nimpl super::Story for DialogStory {\n    fn title() -> &'static str {\n        \"Dialog\"\n    }\n\n    fn description() -> &'static str {\n        \"A dialog dialog\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl DialogStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let input1 = cx.new(|cx| InputState::new(window, cx).placeholder(\"Your Name\"));\n        let input2 = cx.new(|cx| {\n            InputState::new(window, cx).placeholder(\"For test focus back on dialog close.\")\n        });\n        let date = cx.new(|cx| DatePickerState::new(window, cx));\n        let select = cx.new(|cx| {\n            SelectState::new(\n                vec![\"Option 1\".to_string(), \"Option 2\".to_string(), \"Option 3\".to_string()],\n                None,\n                window,\n                cx,\n            )\n        });\n\n        let table = cx.new(|cx| TableState::new(MyTable::new(cx), window, cx));\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            selected_value: None,\n            input1,\n            input2,\n            date,\n            select,\n            dialog_overlay: true,\n            close_button: true,\n            keyboard: true,\n            overlay_closable: true,\n            table,\n        }\n    }\n\n    fn on_action_test_action(\n        &mut self,\n        _: &TestAction,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        window.push_notification(\"You have clicked the TestAction.\", cx);\n    }\n\n    fn render_basic_dialog(&self, cx: &mut Context<Self>) -> impl IntoElement {\n        let dialog_overlay = self.dialog_overlay;\n        let overlay_closable = self.overlay_closable;\n        let input1 = self.input1.clone();\n        let date = self.date.clone();\n        let select = self.select.clone();\n        let view = cx.entity();\n\n        section(\"Basic Dialog\").child(\n            Dialog::new(cx)\n                .trigger(Button::new(\"show-dialog\").outline().label(\"Open Dialog\"))\n                .overlay(dialog_overlay)\n                .keyboard(self.keyboard)\n                .close_button(self.close_button)\n                .overlay_closable(overlay_closable)\n                .on_ok({\n                    let view = view.clone();\n                    let input1 = input1.clone();\n                    let date = date.clone();\n                    move |_, window, cx| {\n                        view.update(cx, |view, cx| {\n                            view.selected_value = Some(\n                                format!(\n                                    \"Hello, {}, date: {}\",\n                                    input1.read(cx).value(),\n                                    date.read(cx).date()\n                                )\n                                .into(),\n                            )\n                        });\n                        window.push_notification(\"You have pressed confirm.\", cx);\n                        true\n                    }\n                })\n                .p_0()\n                .content({\n                    move |content, _, cx| {\n                        content\n                            .child(\n                                DialogHeader::new()\n                                    .p_4()\n                                    .child(DialogTitle::new().child(\"Basic Dialog\"))\n                                    .child(DialogDescription::new().child(\n                                        \"This is a basic dialog created \\\n                                        using the declarative API.\",\n                                    )),\n                            )\n                            .child(\n                                v_flex()\n                                    .px_4()\n                                    .pb_4()\n                                    .gap_3()\n                                    .child(\n                                        \"This is a dialog dialog, \\\n                                        you can put anything here.\",\n                                    )\n                                    .child(Input::new(&input1))\n                                    .child(Select::new(&select))\n                                    .child(DatePicker::new(&date).placeholder(\"Date of Birth\")),\n                            )\n                            .child(\n                                DialogFooter::new()\n                                    .p_4()\n                                    .bg(cx.theme().muted)\n                                    .justify_between()\n                                    .child(\n                                        Button::new(\"new-dialog\")\n                                            .label(\"Open Other Dialog\")\n                                            .outline()\n                                            .on_click(move |_, window, cx| {\n                                                window.open_dialog(cx, move |dialog, _, _| {\n                                                    dialog\n                                                        .title(\"Other Dialog\")\n                                                        .child(\"This is another dialog.\")\n                                                        .min_h(px(100.))\n                                                        .overlay_closable(overlay_closable)\n                                                });\n                                            }),\n                                    )\n                                    .child(\n                                        h_flex()\n                                            .gap_2()\n                                            .child(DialogClose::new().child(\n                                                Button::new(\"cancel\").label(\"Cancel\").outline(),\n                                            ))\n                                            .child(DialogAction::new().child(\n                                                Button::new(\"confirm\").primary().label(\"Confirm\"),\n                                            )),\n                                    ),\n                            )\n                    }\n                }),\n        )\n    }\n\n    fn render_focus_back_test(&self, _cx: &mut Context<Self>) -> impl IntoElement {\n        section(\"Focus back test\").max_w_md().child(Input::new(&self.input2)).child(\n            Button::new(\"test-action\")\n                .outline()\n                .label(\"Test Action\")\n                .flex_shrink_0()\n                .on_click(|_, window, cx| {\n                    window.dispatch_action(Box::new(TestAction), cx);\n                })\n                .tooltip(\n                    \"This button for test dispatch action, \\\n                        to make sure when Dialog close,\\\n                        \\nthis still can handle the action.\",\n                ),\n        )\n    }\n\n    fn render_dialog_without_title(&self, cx: &mut Context<Self>) -> impl IntoElement {\n        let dialog_overlay = self.dialog_overlay;\n        let overlay_closable = self.overlay_closable;\n\n        section(\"Dialog without Title\").child(\n            Button::new(\"dialog-no-title\").outline().label(\"Dialog without Title\").on_click(\n                cx.listener(move |_, _, window, cx| {\n                    window.open_dialog(cx, move |dialog, _, _| {\n                        dialog.overlay(dialog_overlay).overlay_closable(overlay_closable).child(\n                            \"This is a dialog without title, \\\n                                you can use it when the title is not necessary.\",\n                        )\n                    });\n                }),\n            ),\n        )\n    }\n\n    fn render_custom_buttons(&self, cx: &mut Context<Self>) -> impl IntoElement {\n        let dialog_overlay = self.dialog_overlay;\n        let overlay_closable = self.overlay_closable;\n\n        section(\"Custom buttons\").child(\n            Button::new(\"confirm-dialog1\").outline().label(\"Custom Buttons\").on_click(cx.listener(\n                move |_, _, window, cx| {\n                    window.open_dialog(cx, move |dialog, _, cx| {\n                        dialog\n                            .rounded(cx.theme().radius_lg)\n                            .overlay(dialog_overlay)\n                            .overlay_closable(overlay_closable)\n                            .child(\n                                v_flex()\n                                    .gap_3()\n                                    .items_center()\n                                    .child(\n                                        div()\n                                            .flex()\n                                            .items_center()\n                                            .justify_center()\n                                            .rounded(cx.theme().radius_lg)\n                                            .bg(cx.theme().warning.opacity(0.2))\n                                            .size_12()\n                                            .text_color(cx.theme().warning)\n                                            .child(Icon::new(IconName::TriangleAlert).size_8()),\n                                    )\n                                    .child(\n                                        \"Update successful, \\\n                                        we need to restart the application.\",\n                                    ),\n                            )\n                            .footer(\n                                DialogFooter::new()\n                                    .child(\n                                        DialogClose::new()\n                                            .child(Button::new(\"cancel\").label(\"Later\").outline()),\n                                    )\n                                    .child(\n                                        DialogAction::new().child(\n                                            Button::new(\"ok\").label(\"Restart Now\").primary(),\n                                        ),\n                                    ),\n                            )\n                            .on_ok(|_, window, cx| {\n                                window.push_notification(\"You have pressed restart.\", cx);\n                                true\n                            })\n                            .on_cancel(|_, window, cx| {\n                                window.push_notification(\"You have pressed later.\", cx);\n                                true\n                            })\n                    });\n                },\n            )),\n        )\n    }\n\n    fn render_scrollable_dialog(&self, cx: &mut Context<Self>) -> impl IntoElement {\n        let dialog_overlay = self.dialog_overlay;\n        let overlay_closable = self.overlay_closable;\n\n        section(\"Scrollable Dialog\").child(\n            Button::new(\"scrollable-dialog\").outline().label(\"Scrollable Dialog\").on_click(\n                cx.listener(move |_, _, window, cx| {\n                    window.open_dialog(cx, move |dialog, _, _| {\n                        dialog\n                            .w(px(720.))\n                            .h(px(600.))\n                            .overlay(dialog_overlay)\n                            .overlay_closable(overlay_closable)\n                            .title(\"Dialog with scrollbar\")\n                            .child(markdown(include_str!(\"../../../../README.md\")))\n                            .footer(\n                                DialogFooter::new()\n                                    .gap_2()\n                                    .child(\n                                        DialogClose::new()\n                                            .child(Button::new(\"cancel\").label(\"Cancel\").outline()),\n                                    )\n                                    .child(\n                                        DialogAction::new().child(\n                                            Button::new(\"confirm\").label(\"Confirm\").primary(),\n                                        ),\n                                    ),\n                            )\n                    });\n                }),\n            ),\n        )\n    }\n\n    fn render_table_in_dialog(&self, cx: &mut Context<Self>) -> impl IntoElement {\n        let dialog_overlay = self.dialog_overlay;\n        let overlay_closable = self.overlay_closable;\n\n        section(\"Table in Dialog\").child(\n            Button::new(\"table-dialog\").outline().label(\"Table Dialog\").on_click(cx.listener({\n                move |this, _, window, cx| {\n                    window.open_dialog(cx, {\n                        let table = this.table.clone();\n                        move |dialog, _, _| {\n                            dialog\n                                .w(px(800.))\n                                .h(px(600.))\n                                .overlay(dialog_overlay)\n                                .overlay_closable(overlay_closable)\n                                .title(\"Dialog with Table\")\n                                .child(\n                                    v_flex()\n                                        .size_full()\n                                        .gap_3()\n                                        .child(\"This is a dialog contains a table component.\")\n                                        .child(DataTable::new(&table)),\n                                )\n                        }\n                    });\n                }\n            })),\n        )\n    }\n\n    fn render_custom_paddings(&self, cx: &mut Context<Self>) -> impl IntoElement {\n        section(\"Custom Paddings\").child(\n            Button::new(\"custom-dialog-paddings\").outline().label(\"Custom Paddings\").on_click(\n                cx.listener(move |_, _, window, cx| {\n                    window.open_dialog(cx, move |dialog, _, _| {\n                        dialog.p_3().title(\"Custom Dialog Title\").child(\n                            \"This is a custom dialog content, we can use \\\n                            paddings to control the layout and spacing within \\\n                            the dialog.\",\n                        )\n                    });\n                }),\n            ),\n        )\n    }\n\n    fn render_custom_style(&self, cx: &mut Context<Self>) -> impl IntoElement {\n        section(\"Custom Style\").child(\n            Button::new(\"custom-dialog-style\").outline().label(\"Custom Dialog Style\").on_click(\n                cx.listener(move |_, _, window, cx| {\n                    window.open_dialog(cx, move |dialog, _, cx| {\n                        dialog\n                            .rounded(cx.theme().radius_lg)\n                            .bg(cx.theme().cyan)\n                            .text_color(cx.theme().info_foreground)\n                            .title(\"Custom Dialog Title\")\n                            .child(\"This is a custom dialog content.\")\n                    });\n                }),\n            ),\n        )\n    }\n\n    fn render_dialog_with_content(&self, cx: &mut Context<Self>) -> impl IntoElement {\n        section(\"Open Dialog with DialogContent\").sub_title(\"Declarative API\").child(\n            Button::new(\"custom-width-dialog-btn\")\n                .outline()\n                .label(\"Custom Width (400px)\")\n                .on_click(cx.listener(move |_, _, window, cx| {\n                    window.open_dialog(cx, move |dialog, _, _| {\n                        dialog.w(px(400.)).content(|content, _, _| {\n                            content\n                                .child(\n                                    DialogHeader::new()\n                                        .child(DialogTitle::new().child(\"Custom Width\"))\n                                        .child(\n                                            DialogDescription::new()\n                                                .child(\"This dialog has a custom width of 400px.\"),\n                                        ),\n                                )\n                                .child(\n                                    \"Content area with custom width configuration, \\\n                                            and the footer is used flex 1 button widths.\",\n                                )\n                                .child(\n                                    DialogFooter::new()\n                                        .justify_center()\n                                        .child(\n                                            Button::new(\"cancel\")\n                                                .flex_1()\n                                                .outline()\n                                                .label(\"Cancel\")\n                                                .on_click(|_, window, cx| {\n                                                    window.close_dialog(cx);\n                                                }),\n                                        )\n                                        .child(\n                                            Button::new(\"done\")\n                                                .flex_1()\n                                                .primary()\n                                                .label(\"Done\")\n                                                .on_click(|_, window, cx| {\n                                                    window.close_dialog(cx);\n                                                }),\n                                        ),\n                                )\n                        })\n                    })\n                })),\n        )\n    }\n}\n\nimpl Focusable for DialogStory {\n    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for DialogStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .id(\"dialog-story\")\n            .track_focus(&self.focus_handle)\n            .on_action(cx.listener(Self::on_action_test_action))\n            .size_full()\n            .child(\n                v_flex()\n                    .gap_6()\n                    .child(\n                        h_flex()\n                            .items_center()\n                            .gap_3()\n                            .child(\n                                Checkbox::new(\"dialog-overlay\")\n                                    .label(\"Dialog Overlay\")\n                                    .checked(self.dialog_overlay)\n                                    .on_click(cx.listener(|view, _, _, cx| {\n                                        view.dialog_overlay = !view.dialog_overlay;\n                                        cx.notify();\n                                    })),\n                            )\n                            .child(\n                                Checkbox::new(\"overlay-closable\")\n                                    .label(\"Overlay Closable\")\n                                    .checked(self.overlay_closable)\n                                    .on_click(cx.listener(|view, _, _, cx| {\n                                        view.overlay_closable = !view.overlay_closable;\n                                        cx.notify();\n                                    })),\n                            )\n                            .child(\n                                Checkbox::new(\"dialog-show-close\")\n                                    .label(\"Model Close Button\")\n                                    .checked(self.close_button)\n                                    .on_click(cx.listener(|view, _, _, cx| {\n                                        view.close_button = !view.close_button;\n                                        cx.notify();\n                                    })),\n                            )\n                            .child(\n                                Checkbox::new(\"dialog-keyboard\")\n                                    .label(\"Keyboard\")\n                                    .checked(self.keyboard)\n                                    .on_click(cx.listener(|view, _, _, cx| {\n                                        view.keyboard = !view.keyboard;\n                                        cx.notify();\n                                    })),\n                            ),\n                    )\n                    .child(self.render_basic_dialog(cx))\n                    .child(self.render_focus_back_test(cx))\n                    .child(self.render_custom_buttons(cx))\n                    .child(self.render_scrollable_dialog(cx))\n                    .child(self.render_table_in_dialog(cx))\n                    .child(self.render_dialog_without_title(cx))\n                    .child(self.render_custom_paddings(cx))\n                    .child(self.render_custom_style(cx))\n                    .child(self.render_dialog_with_content(cx)),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/divider_story.rs",
    "content": "use crate::section;\nuse gpui::{\n    App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled,\n    Window, px,\n};\nuse gpui_component::{ActiveTheme, divider::Divider, h_flex, label::Label, v_flex};\n\nconst DESCRIPTION: &str = \"GPUI Component is a Rust GUI components for building fantastic cross-platform desktop application by using GPUI.\";\n\npub struct DividerStory {\n    focus_handle: gpui::FocusHandle,\n}\n\nimpl super::Story for DividerStory {\n    fn title() -> &'static str {\n        \"Divider\"\n    }\n\n    fn description() -> &'static str {\n        \"A divider that can be either vertical or horizontal.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl DividerStory {\n    pub fn view(_window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self {\n            focus_handle: cx.focus_handle(),\n        })\n    }\n}\n\nimpl Focusable for DividerStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for DividerStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_6()\n            .child(\n                section(\"Horizontal Dividers\").child(\n                    v_flex()\n                        .gap_4()\n                        .w_full()\n                        .mt_4()\n                        .child(Divider::horizontal())\n                        .child(Divider::horizontal().label(\"With Label\"))\n                        .child(Divider::horizontal_dashed())\n                        .child(Divider::horizontal_dashed().label(\"Dashed With Label\")),\n                ),\n            )\n            .child(\n                section(\"Vertical Dividers\").child(\n                    h_flex()\n                        .gap_4()\n                        .h(px(100.))\n                        .child(Divider::vertical())\n                        .child(Divider::vertical().label(\"Solid\"))\n                        .child(Divider::vertical_dashed())\n                        .child(Divider::vertical_dashed().label(\"Dashed\")),\n                ),\n            )\n            .child(\n                section(\"Combination Dividers\").child(\n                    v_flex()\n                        .gap_y_4()\n                        .child(\n                            v_flex().gap_y_2().child(\"Hello GPUI Component\").child(\n                                Label::new(DESCRIPTION)\n                                    .text_color(cx.theme().muted_foreground)\n                                    .text_sm(),\n                            ),\n                        )\n                        .child(Divider::horizontal())\n                        .child(\n                            h_flex()\n                                .gap_x_4()\n                                .child(\"Docs\")\n                                .child(Divider::vertical().dashed())\n                                .child(\"Github\")\n                                .child(Divider::vertical().dashed())\n                                .child(\"Source\"),\n                        ),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/dropdown_button_story.rs",
    "content": "use gpui::{\n    Action, App, AppContext as _, Context, Corner, Entity, Focusable, IntoElement,\n    ParentElement as _, Render, Styled as _, Window, prelude::FluentBuilder as _,\n};\nuse serde::Deserialize;\n\nuse crate::section;\nuse gpui_component::{\n    ActiveTheme, Disableable, Selectable as _, Sizable as _, Theme,\n    button::{Button, ButtonVariants as _, DropdownButton},\n    checkbox::Checkbox,\n    h_flex, v_flex,\n};\n\n#[derive(Clone, Action, PartialEq, Eq, Deserialize)]\n#[action(namespace = dropdown_button_story, no_json)]\nenum ButtonAction {\n    Disabled,\n    Loading,\n    Selected,\n    Compact,\n}\n\npub struct DropdownButtonStory {\n    focus_handle: gpui::FocusHandle,\n    disabled: bool,\n    loading: bool,\n    selected: bool,\n    compact: bool,\n}\n\nimpl DropdownButtonStory {\n    pub fn view(_: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self {\n            focus_handle: cx.focus_handle(),\n            disabled: false,\n            loading: false,\n            selected: false,\n            compact: false,\n        })\n    }\n}\n\nimpl super::Story for DropdownButtonStory {\n    fn title() -> &'static str {\n        \"DropdownButton\"\n    }\n\n    fn description() -> &'static str {\n        \"A button with an attached dropdown menu for additional options.\"\n    }\n\n    fn closable() -> bool {\n        false\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl Focusable for DropdownButtonStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for DropdownButtonStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let disabled = self.disabled;\n        let loading = self.loading;\n        let selected = self.selected;\n        let compact = self.compact;\n\n        v_flex()\n            .gap_6()\n            .child(\n                h_flex()\n                    .gap_3()\n                    .child(\n                        Checkbox::new(\"disabled-button\")\n                            .label(\"Disabled\")\n                            .checked(self.disabled)\n                            .on_click(cx.listener(|view, _, _, cx| {\n                                view.disabled = !view.disabled;\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"loading-button\")\n                            .label(\"Loading\")\n                            .checked(self.loading)\n                            .on_click(cx.listener(|view, _, _, cx| {\n                                view.loading = !view.loading;\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"selected-button\")\n                            .label(\"Selected\")\n                            .checked(self.selected)\n                            .on_click(cx.listener(|view, _, _, cx| {\n                                view.selected = !view.selected;\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"compact-button\")\n                            .label(\"Compact\")\n                            .checked(self.compact)\n                            .on_click(cx.listener(|view, _, _, cx| {\n                                view.compact = !view.compact;\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"shadow-button\")\n                            .label(\"Shadow\")\n                            .checked(cx.theme().shadow)\n                            .on_click(cx.listener(|_, _, window, cx| {\n                                let mut theme = cx.theme().clone();\n                                theme.shadow = !theme.shadow;\n                                cx.set_global::<Theme>(theme);\n                                window.refresh();\n                            })),\n                    ),\n            )\n            .child(\n                section(\"Dropdown Button\").child(\n                    DropdownButton::new(\"btn0\")\n                        .primary()\n                        .button(Button::new(\"btn\").label(\"Primary Dropdown\"))\n                        .when(self.compact, |this| this.compact())\n                        .loading(self.loading)\n                        .disabled(self.disabled)\n                        .selected(selected)\n                        .dropdown_menu_with_anchor(Corner::BottomRight, move |this, _, _| {\n                            this.menu_with_check(\n                                \"Disabled\",\n                                disabled,\n                                Box::new(ButtonAction::Disabled),\n                            )\n                            .menu_with_check(\"Loading\", loading, Box::new(ButtonAction::Loading))\n                            .menu_with_check(\"Selected\", selected, Box::new(ButtonAction::Selected))\n                            .menu_with_check(\n                                \"Compact\",\n                                compact,\n                                Box::new(ButtonAction::Compact),\n                            )\n                        }),\n                ),\n            )\n            .child(\n                section(\"Small Size\").child(\n                    DropdownButton::new(\"btn-sm\")\n                        .small()\n                        .button(Button::new(\"btn\").label(\"Small Dropdown\"))\n                        .when(self.compact, |this| this.compact())\n                        .loading(self.loading)\n                        .disabled(self.disabled)\n                        .selected(selected)\n                        .dropdown_menu(move |this, _, _| {\n                            this.menu_with_check(\n                                \"Disabled\",\n                                disabled,\n                                Box::new(ButtonAction::Disabled),\n                            )\n                            .menu_with_check(\"Loading\", loading, Box::new(ButtonAction::Loading))\n                            .menu_with_check(\"Selected\", selected, Box::new(ButtonAction::Selected))\n                            .menu_with_check(\n                                \"Compact\",\n                                compact,\n                                Box::new(ButtonAction::Compact),\n                            )\n                        }),\n                ),\n            )\n            .child(\n                section(\"Outline\").child(\n                    DropdownButton::new(\"btn-outline\")\n                        .outline()\n                        .danger()\n                        .button(Button::new(\"btn\").label(\"Outline Dropdown\"))\n                        .when(self.compact, |this| this.compact())\n                        .loading(self.loading)\n                        .disabled(self.disabled)\n                        .selected(selected)\n                        .dropdown_menu(move |this, _, _| {\n                            this.menu_with_check(\n                                \"Disabled\",\n                                disabled,\n                                Box::new(ButtonAction::Disabled),\n                            )\n                            .menu_with_check(\"Loading\", loading, Box::new(ButtonAction::Loading))\n                            .menu_with_check(\"Selected\", selected, Box::new(ButtonAction::Selected))\n                            .menu_with_check(\n                                \"Compact\",\n                                compact,\n                                Box::new(ButtonAction::Compact),\n                            )\n                        }),\n                ),\n            )\n            .child(\n                section(\"Ghost\").child(\n                    DropdownButton::new(\"btn-ghost\")\n                        .ghost()\n                        .button(Button::new(\"btn\").label(\"Ghost Dropdown\"))\n                        .when(self.compact, |this| this.compact())\n                        .loading(self.loading)\n                        .disabled(self.disabled)\n                        .selected(selected)\n                        .dropdown_menu(move |this, _, _| {\n                            this.menu_with_check(\n                                \"Disabled\",\n                                disabled,\n                                Box::new(ButtonAction::Disabled),\n                            )\n                            .menu_with_check(\"Loading\", loading, Box::new(ButtonAction::Loading))\n                            .menu_with_check(\"Selected\", selected, Box::new(ButtonAction::Selected))\n                            .menu_with_check(\n                                \"Compact\",\n                                compact,\n                                Box::new(ButtonAction::Compact),\n                            )\n                        }),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/editor_story.rs",
    "content": "use gpui::{App, AppContext as _, Context, Entity, IntoElement, Render, Styled, Window};\n\nuse gpui_component::input::*;\n\nconst EXAMPLE_CODE: &str = include_str!(\"./editor_story.rs\");\n\npub struct EditorStory {\n    editor_state: Entity<InputState>,\n}\n\nimpl super::Story for EditorStory {\n    fn title() -> &'static str {\n        \"Editor\"\n    }\n\n    fn description() -> &'static str {\n        \"Code editor with syntax highlighting by tree-sitter.\"\n    }\n\n    fn closable() -> bool {\n        false\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl EditorStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let editor_state = cx.new(|cx| {\n            InputState::new(window, cx)\n                .code_editor(\"rust\")\n                .multi_line(true)\n                .tab_size(TabSize {\n                    tab_size: 4,\n                    ..Default::default()\n                })\n                .default_value(EXAMPLE_CODE)\n        });\n\n        Self { editor_state }\n    }\n}\n\nimpl Render for EditorStory {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        Input::new(&self.editor_state).size_full()\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/form_story.rs",
    "content": "use gpui::{\n    App, AppContext, Axis, Context, Entity, FocusHandle, Focusable, InteractiveElement,\n    IntoElement, ParentElement as _, Render, Styled, Window, div, prelude::FluentBuilder as _, px,\n};\nuse gpui_component::{\n    ActiveTheme, AxisExt, IndexPath, Selectable, Sizable, Size,\n    button::{Button, ButtonGroup},\n    checkbox::Checkbox,\n    color_picker::{ColorPicker, ColorPickerState},\n    date_picker::{DatePicker, DatePickerState},\n    divider::Divider,\n    form::{field, v_form},\n    h_flex,\n    input::{Input, InputState},\n    select::{Select, SelectState},\n    switch::Switch,\n    v_flex,\n};\n\npub struct FormStory {\n    focus_handle: FocusHandle,\n    name_prefix_state: Entity<SelectState<Vec<String>>>,\n    name_input: Entity<InputState>,\n    email_input: Entity<InputState>,\n    bio_input: Entity<InputState>,\n    color_state: Entity<ColorPickerState>,\n    subscribe_email: bool,\n    date: Entity<DatePickerState>,\n    layout: Axis,\n    size: Size,\n    columns: usize,\n}\n\nimpl super::Story for FormStory {\n    fn title() -> &'static str {\n        \"Form\"\n    }\n\n    fn description() -> &'static str {\n        \"Form to collect multiple inputs.\"\n    }\n\n    fn closable() -> bool {\n        false\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl FormStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let name_prefix_state = cx.new(|cx| {\n            SelectState::new(\n                vec![\n                    \"Mr.\".to_string(),\n                    \"Mrs.\".to_string(),\n                    \"Ms.\".to_string(),\n                    \"Dr.\".to_string(),\n                ],\n                Some(IndexPath::default()),\n                window,\n                cx,\n            )\n        });\n\n        let name_input = cx.new(|cx| InputState::new(window, cx).default_value(\"Jason Lee\"));\n        let color_state = cx.new(|cx| ColorPickerState::new(window, cx));\n\n        let email_input =\n            cx.new(|cx| InputState::new(window, cx).placeholder(\"Enter text here...\"));\n        let bio_input = cx.new(|cx| {\n            InputState::new(window, cx)\n                .auto_grow(5, 20)\n                .placeholder(\"Enter text here...\")\n                .default_value(\"Hello 世界，this is GPUI component.\")\n        });\n        let date = cx.new(|cx| DatePickerState::new(window, cx));\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            name_prefix_state,\n            name_input,\n            email_input,\n            bio_input,\n            date,\n            color_state,\n            subscribe_email: false,\n            layout: Axis::Vertical,\n            size: Size::default(),\n            columns: 1,\n        }\n    }\n}\n\nimpl Focusable for FormStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for FormStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let is_multi_column = self.columns > 1;\n        let is_horizontal = self.layout.is_horizontal();\n\n        v_flex()\n            .id(\"form-story\")\n            .size_full()\n            .p_4()\n            .justify_start()\n            .gap_3()\n            .child(\n                h_flex()\n                    .gap_3()\n                    .flex_wrap()\n                    .justify_between()\n                    .child(\n                        h_flex()\n                            .gap_x_3()\n                            .child(\n                                Switch::new(\"layout\")\n                                    .checked(self.layout.is_horizontal())\n                                    .label(\"Horizontal\")\n                                    .on_click(cx.listener(|this, checked: &bool, _, cx| {\n                                        if *checked {\n                                            this.layout = Axis::Horizontal;\n                                        } else {\n                                            this.layout = Axis::Vertical;\n                                        }\n                                        cx.notify();\n                                    })),\n                            )\n                            .child(\n                                Switch::new(\"column\")\n                                    .checked(self.columns > 1)\n                                    .label(\"Multi Columns\")\n                                    .on_click(cx.listener(|this, checked: &bool, _, cx| {\n                                        if *checked {\n                                            this.columns = 2;\n                                        } else {\n                                            this.columns = 1;\n                                        }\n                                        cx.notify();\n                                    })),\n                            ),\n                    )\n                    .child(\n                        ButtonGroup::new(\"size\")\n                            .outline()\n                            .small()\n                            .child(\n                                Button::new(\"large\")\n                                    .selected(self.size == Size::Large)\n                                    .child(\"Large\"),\n                            )\n                            .child(\n                                Button::new(\"medium\")\n                                    .child(\"Medium\")\n                                    .selected(self.size == Size::Medium),\n                            )\n                            .child(\n                                Button::new(\"small\")\n                                    .child(\"Small\")\n                                    .selected(self.size == Size::Small),\n                            )\n                            .on_click(cx.listener(|this, selecteds: &Vec<usize>, _, cx| {\n                                if selecteds.contains(&0) {\n                                    this.size = Size::Large;\n                                } else if selecteds.contains(&1) {\n                                    this.size = Size::Medium;\n                                } else if selecteds.contains(&2) {\n                                    this.size = Size::Small;\n                                }\n                                cx.notify();\n                            })),\n                    ),\n            )\n            .child(Divider::horizontal())\n            .child(\n                v_form()\n                    .layout(self.layout)\n                    .with_size(self.size)\n                    .columns(self.columns)\n                    .label_width(px(if is_multi_column { 100. } else { 140. }))\n                    .child(\n                        field().label_fn(|_, _| \"Name\").child(\n                            h_flex()\n                                .gap_2()\n                                .border_1()\n                                .border_color(cx.theme().input)\n                                .bg(cx.theme().input_background())\n                                .rounded(cx.theme().radius)\n                                .child(\n                                    div().w(px(90.)).child(\n                                        Select::new(&self.name_prefix_state)\n                                            .pr_0()\n                                            .appearance(false),\n                                    ),\n                                )\n                                .child(\n                                    div().flex_1().child(\n                                        Input::new(&self.name_input).pl_0().appearance(false),\n                                    ),\n                                ),\n                        ),\n                    )\n                    .child(\n                        field()\n                            .label(\"Email\")\n                            .child(Input::new(&self.email_input))\n                            .required(true),\n                    )\n                    .child(\n                        field()\n                            .label(\"Bio\")\n                            .when(self.layout.is_vertical(), |this| this.items_start())\n                            .child(Input::new(&self.bio_input))\n                            .description_fn(|_, _| {\n                                div().child(\"Use at most 100 words to describe yourself.\")\n                            }),\n                    )\n                    .child(\n                        field()\n                            .label_indent(false)\n                            .when(is_multi_column, |this| this.col_span(2))\n                            .child(\"This is a full width form field.\"),\n                    )\n                    .child(\n                        field()\n                            .label(\"Please select your birthday\")\n                            .description(\"Select your birthday, we will send you a gift.\")\n                            .child(DatePicker::new(&self.date)),\n                    )\n                    .child(\n                        field()\n                            .when(is_horizontal && is_multi_column, |this| {\n                                this.label_indent(false)\n                            })\n                            .when(is_multi_column, |this| this.col_start(1))\n                            .child(\n                                Switch::new(\"subscribe-newsletter\")\n                                    .label(\"Subscribe our newsletter\")\n                                    .checked(self.subscribe_email)\n                                    .on_click(cx.listener(|this, checked: &bool, _, cx| {\n                                        this.subscribe_email = *checked;\n                                        cx.notify();\n                                    })),\n                            ),\n                    )\n                    .child(\n                        field()\n                            .when(is_horizontal && is_multi_column, |this| {\n                                this.label_indent(false)\n                            })\n                            .child(\n                                ColorPicker::new(&self.color_state)\n                                    .small()\n                                    .label(\"Theme color\"),\n                            ),\n                    )\n                    .child(\n                        field()\n                            .when(is_horizontal && is_multi_column, |this| {\n                                this.label_indent(false)\n                            })\n                            .child(\n                                Checkbox::new(\"use-vertical-layout\")\n                                    .label(\"Vertical layout\")\n                                    .checked(self.layout.is_vertical())\n                                    .on_click(cx.listener(|this, checked: &bool, _, cx| {\n                                        this.layout = if *checked {\n                                            Axis::Vertical\n                                        } else {\n                                            Axis::Horizontal\n                                        };\n                                        cx.notify();\n                                    })),\n                            ),\n                    ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/group_box_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render,\n    StyleRefinement, Styled, Window, relative,\n};\n\nuse gpui_component::{\n    ActiveTheme as _, StyledExt,\n    button::{Button, ButtonVariants},\n    checkbox::Checkbox,\n    group_box::{GroupBox, GroupBoxVariants as _},\n    h_flex,\n    radio::{Radio, RadioGroup},\n    switch::Switch,\n    text::markdown,\n    v_flex,\n};\n\nuse crate::section;\n\npub struct GroupBoxStory {\n    focus_handle: gpui::FocusHandle,\n}\n\nimpl super::Story for GroupBoxStory {\n    fn title() -> &'static str {\n        \"GroupBox\"\n    }\n\n    fn description() -> &'static str {\n        \"A styled container element that with an optional title \\\n        to groups related content together.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl GroupBoxStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n}\n\nimpl Focusable for GroupBoxStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for GroupBoxStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .size_full()\n            .justify_center()\n            .gap_4()\n            .child(\n                section(\"Default Style\").w_128().child(\n                    GroupBox::new()\n                        .child(\"Subscriptions\")\n                        .child(Checkbox::new(\"all\").label(\"All\"))\n                        .child(Checkbox::new(\"news-letter\").label(\"News Letter\"))\n                        .child(Checkbox::new(\"account-activity\").label(\"Account Activity\"))\n                        .child(Button::new(\"ok\").primary().label(\"Update Subscriptions\")),\n                ),\n            )\n            .child(\n                section(\"Fill Style\").w_128().child(\n                    GroupBox::new()\n                        .id(\"activity\")\n                        .fill()\n                        .title(\"Contributions & activity\")\n                        .child(\n                            h_flex()\n                                .justify_between()\n                                .child(\"Make profile private and hide activity\")\n                                .child(Switch::new(\"toggle-0\").checked(true)),\n                        )\n                        .child(\n                            h_flex()\n                                .justify_between()\n                                .child(\"Include private contributions on my profile\")\n                                .child(Switch::new(\"toggle-1\").checked(false)),\n                        )\n                        .child(Button::new(\"btn-1\").primary().label(\"Save\")),\n                ),\n            )\n            .child(\n                section(\"Outline Style\").w_128().child(\n                    GroupBox::new()\n                        .id(\"appearance\")\n                        .outline()\n                        .title(\"Appearance\")\n                        .child(\n                            RadioGroup::vertical(\"theme\")\n                                .child(Radio::new(\"light\").label(\"Light\"))\n                                .child(Radio::new(\"dark\").label(\"Dark\"))\n                                .child(Radio::new(\"system\").label(\"System\")),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Without Title\").w_128().child(\n                    GroupBox::new().outline().child(\n                        h_flex()\n                            .justify_between()\n                            .child(\"Make profile private and hide activity\")\n                            .child(Switch::new(\"toggle-1\").checked(true)),\n                    ),\n                ),\n            )\n            .child(\n                section(\"Custom style\").w_128().child(\n                    GroupBox::new()\n                        .outline()\n                        .bg(cx.theme().group_box)\n                        .rounded_xl()\n                        .p_5()\n                        .title(\"This is a custom style\")\n                        .title_style(\n                            StyleRefinement::default()\n                                .font_semibold()\n                                .line_height(relative(1.0))\n                                .px_3(),\n                        )\n                        .content_style(\n                            StyleRefinement::default()\n                                .rounded_xl()\n                                .py_3()\n                                .px_4()\n                                .border_2(),\n                        )\n                        .child(markdown(\n                            \"You can use `title_style` to customize the style \\\n                                of the title. \\n \\\n                                And any style in `GroupBox` will apply to the content container.\",\n                        )),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/hover_card_story.rs",
    "content": "use gpui::{\n    App, AppContext as _, Context, Entity, IntoElement, ParentElement as _, Render, Styled as _,\n    Window, div, px, relative,\n};\nuse gpui_component::{\n    ActiveTheme, Anchor, StyledExt, avatar::Avatar, button::Button, h_flex, hover_card::HoverCard,\n    v_flex,\n};\nuse std::time::Duration;\n\nuse crate::{Story, section};\n\npub struct HoverCardStory {}\n\nimpl HoverCardStory {\n    fn new(_: &mut Window, _: &mut Context<Self>) -> Self {\n        Self {}\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\n\nimpl Render for HoverCardStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_6()\n            .child(self.render_basic_example(cx))\n            .child(self.render_user_profile_example(cx))\n            .child(self.render_custom_timing_example(cx))\n            .child(self.render_positioning_examples(cx))\n    }\n}\n\nimpl HoverCardStory {\n    /// Basic hover card example\n    fn render_basic_example(&self, cx: &mut Context<Self>) -> impl IntoElement {\n        section(\"Basic\").child(\n            HoverCard::new(\"basic\")\n                .trigger(\n                    div()\n                        .child(\"Hover over me\")\n                        .text_color(cx.theme().primary)\n                        .cursor_pointer()\n                        .text_sm(),\n                )\n                .child(\n                    v_flex()\n                        .gap_1()\n                        .w(px(450.))\n                        .child(\n                            div()\n                                .child(\"This is a hover card\")\n                                .font_semibold()\n                                .text_sm(),\n                        )\n                        .child(\n                            div()\n                                .child(\"You can display rich content when hovering over a trigger element.\")\n                                .text_color(cx.theme().muted_foreground)\n                                .text_sm(),\n                        ),\n                ),\n        )\n    }\n\n    fn render_user_profile_example(&self, cx: &mut Context<Self>) -> impl IntoElement {\n        section(\"User Profile Preview\").child(\n            h_flex()\n                .child(\"Hover over \")\n                .child(\n                    HoverCard::new(\"user-profile\")\n                        .trigger(\n                            div()\n                                .child(\"@huacnlee\")\n                                .cursor_pointer()\n                                .text_color(cx.theme().link),\n                        )\n                        .content(|_, _, cx| {\n                            h_flex()\n                                .w(px(320.))\n                                .gap_3()\n                                .items_start()\n                                .child(\n                                    Avatar::new()\n                                        .src(\"https://avatars.githubusercontent.com/u/5518?s=64\"),\n                                )\n                                .child(\n                                    v_flex()\n                                        .gap_1()\n                                        .line_height(relative(1.))\n                                        .child(div().child(\"Jason Lee\").font_semibold())\n                                        .child(\n                                            div()\n                                                .child(\"@huacnlee\")\n                                                .text_color(cx.theme().link)\n                                                .text_sm(),\n                                        )\n                                        .child(div().mt_1().child(\"The author of GPUI Component.\")),\n                                )\n                        }),\n                )\n                .child(\" to see their profile\"),\n        )\n    }\n\n    /// Custom timing configuration example\n    fn render_custom_timing_example(&self, _: &mut Context<Self>) -> impl IntoElement {\n        section(\"Custom Timing\").child(\n            h_flex()\n                .gap_4()\n                .child(\n                    HoverCard::new(\"fast-open\")\n                        .open_delay(Duration::from_millis(200))\n                        .close_delay(Duration::from_millis(100))\n                        .trigger(Button::new(\"fast\").label(\"Fast Open (200ms)\").outline())\n                        .child(div().child(\"This hover card opens after 200ms\").text_sm()),\n                )\n                .child(\n                    HoverCard::new(\"slow-open\")\n                        .open_delay(Duration::from_secs(1))\n                        .close_delay(Duration::from_secs_f32(0.5))\n                        .trigger(Button::new(\"slow\").label(\"Slow Open (1000ms)\").outline())\n                        .child(div().child(\"This hover card opens after 1000ms\").text_sm()),\n                ),\n        )\n    }\n\n    /// All positioning options\n    fn render_positioning_examples(&self, _: &mut Context<Self>) -> impl IntoElement {\n        section(\"Positioning\").child(\n            v_flex()\n                .gap_4()\n                .items_center()\n                .justify_center()\n                .child(\n                    h_flex()\n                        .gap_4()\n                        .child(\n                            HoverCard::new(\"anchor-top-left\")\n                                .anchor(Anchor::TopLeft)\n                                .trigger(Button::new(\"tl\").label(\"Top Left\").outline())\n                                .child(div().child(\"Positioned at Top Left\").text_sm()),\n                        )\n                        .child(\n                            HoverCard::new(\"anchor-top-center\")\n                                .anchor(Anchor::TopCenter)\n                                .trigger(Button::new(\"tc\").label(\"Top Center\").outline())\n                                .child(div().child(\"Positioned at Top Center\").text_sm()),\n                        )\n                        .child(\n                            HoverCard::new(\"anchor-top-right\")\n                                .anchor(Anchor::TopRight)\n                                .trigger(Button::new(\"tr\").label(\"Top Right\").outline())\n                                .child(div().child(\"Positioned at Top Right\").text_sm()),\n                        ),\n                )\n                // Bottom row\n                .child(\n                    h_flex()\n                        .gap_4()\n                        .child(\n                            HoverCard::new(\"anchor-bottom-left\")\n                                .anchor(Anchor::BottomLeft)\n                                .trigger(Button::new(\"bl\").label(\"Bottom Left\").outline())\n                                .child(div().child(\"Positioned at Bottom Left\").text_sm()),\n                        )\n                        .child(\n                            HoverCard::new(\"anchor-bottom-center\")\n                                .anchor(Anchor::BottomCenter)\n                                .trigger(Button::new(\"bc\").label(\"Bottom Center\").outline())\n                                .child(div().child(\"Positioned at Bottom Center\").text_sm()),\n                        )\n                        .child(\n                            HoverCard::new(\"anchor-bottom-right\")\n                                .anchor(Anchor::BottomRight)\n                                .trigger(Button::new(\"br\").label(\"Bottom Right\").outline())\n                                .child(div().child(\"Positioned at Bottom Right\").text_sm()),\n                        ),\n                ),\n        )\n    }\n}\n\nimpl Story for HoverCardStory {\n    fn title() -> &'static str {\n        \"HoverCard\"\n    }\n\n    fn description() -> &'static str {\n        \"A hover card displays content when hovering over a trigger element, with configurable delays.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/icon_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render,\n    Styled, Window,\n};\nuse gpui_component::{\n    button::{Button, ButtonVariant, ButtonVariants},\n    dock::PanelControl,\n    h_flex, neutral_500, v_flex, ActiveTheme as _, Icon, IconName, Sizable,\n};\n\nuse crate::section;\n\npub struct IconStory {\n    focus_handle: gpui::FocusHandle,\n}\n\nimpl IconStory {\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\n\nimpl super::Story for IconStory {\n    fn title() -> &'static str {\n        \"Icon\"\n    }\n\n    fn description() -> &'static str {\n        \"SVG Icons based on Lucide.dev\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n\n    fn zoomable() -> Option<PanelControl> {\n        None\n    }\n}\n\nimpl Focusable for IconStory {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for IconStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_4()\n            .child(\n                section(\"Icon\")\n                    .text_lg()\n                    .child(IconName::Info)\n                    .child(IconName::Map)\n                    .child(IconName::Bot)\n                    .child(IconName::Github)\n                    .child(IconName::Calendar)\n                    .child(IconName::Globe)\n                    .child(IconName::Heart),\n            )\n            .child(\n                section(\"Color Icon\")\n                    .child(\n                        Icon::new(IconName::Maximize)\n                            .size_6()\n                            .text_color(cx.theme().green),\n                    )\n                    .child(\n                        Icon::new(IconName::Minimize)\n                            .size_6()\n                            .text_color(cx.theme().red),\n                    ),\n            )\n            .child(\n                section(\"Icon Button\").child(\n                    h_flex()\n                        .gap_4()\n                        .child(\n                            Button::new(\"like1\")\n                                .icon(\n                                    Icon::new(IconName::Heart)\n                                        .text_color(neutral_500())\n                                        .size_6(),\n                                )\n                                .with_variant(ButtonVariant::Ghost),\n                        )\n                        .child(\n                            Button::new(\"like2\")\n                                .icon(\n                                    Icon::new(IconName::HeartOff)\n                                        .text_color(cx.theme().red)\n                                        .size_6(),\n                                )\n                                .with_variant(ButtonVariant::Ghost),\n                        )\n                        .child(\n                            Button::new(\"like3\")\n                                .icon(\n                                    Icon::new(IconName::Heart)\n                                        .text_color(cx.theme().green)\n                                        .size_6(),\n                                )\n                                .with_variant(ButtonVariant::Ghost),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Button with size\").child(\n                    Button::new(\"button-with-size\")\n                        .outline()\n                        .size_5()\n                        .small()\n                        .px_0()\n                        .label(\"10\"),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/image_story.rs",
    "content": "use crate::section;\nuse gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement as _,\n    Render, Styled, Window, img,\n};\nuse gpui_component::{dock::PanelControl, v_flex};\n\npub struct ImageStory {\n    focus_handle: gpui::FocusHandle,\n}\n\nimpl super::Story for ImageStory {\n    fn title() -> &'static str {\n        \"Image\"\n    }\n\n    fn description() -> &'static str {\n        \"Image and SVG image supported.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n\n    fn zoomable() -> Option<PanelControl> {\n        Some(PanelControl::Toolbar)\n    }\n}\n\nimpl ImageStory {\n    pub fn new(_: &mut Window, cx: &mut App) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\n\nimpl Focusable for ImageStory {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for ImageStory {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex().gap_4().size_full().child(\n            section(\"SVG from URL\")\n                .child(img(\"https://pub.lbkrs.com/files/202503/vEnnmgUM6bo362ya/sdk.svg\").h_24()),\n        )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/input_story.rs",
    "content": "use gpui::{\n    App, AppContext as _, ClickEvent, Context, Entity, InteractiveElement, IntoElement,\n    ParentElement as _, Render, Styled, Subscription, Window, div,\n};\n\nuse crate::section;\nuse gpui_component::{button::*, input::*, *};\n\nconst CODE_EXAMPLE: &str = r#\"{\"single_line\":\"code editor\"}\"#;\n\npub fn init(_: &mut App) {}\n\npub struct InputStory {\n    input1: Entity<InputState>,\n    input2: Entity<InputState>,\n    input_esc: Entity<InputState>,\n    input_text_centered: Entity<InputState>,\n    input_text_right: Entity<InputState>,\n    mask_input: Entity<InputState>,\n    disabled_input: Entity<InputState>,\n    prefix_input1: Entity<InputState>,\n    suffix_input1: Entity<InputState>,\n    both_input1: Entity<InputState>,\n    large_input: Entity<InputState>,\n    small_input: Entity<InputState>,\n    phone_input: Entity<InputState>,\n    mask_input2: Entity<InputState>,\n    currency_input: Entity<InputState>,\n    custom_input: Entity<InputState>,\n    code_input: Entity<InputState>,\n\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl super::Story for InputStory {\n    fn title() -> &'static str {\n        \"Input\"\n    }\n\n    fn closable() -> bool {\n        false\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl InputStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let input1 = cx.new(|cx| {\n            InputState::new(window, cx)\n                .default_value(\"Hello 世界，this is GPUI component, this is a long text.\")\n        });\n\n        let input2 = cx.new(|cx| InputState::new(window, cx).placeholder(\"Enter text here...\"));\n        let input_esc = cx.new(|cx| {\n            InputState::new(window, cx)\n                .placeholder(\"Enter text and clear it by pressing ESC\")\n                .clean_on_escape()\n        });\n\n        let mask_input = cx.new(|cx| {\n            InputState::new(window, cx)\n                .masked(true)\n                .placeholder(\"Enter your password...\")\n                .default_value(\"this-is-password-中文🚀🎉\")\n        });\n\n        let prefix_input1 =\n            cx.new(|cx| InputState::new(window, cx).placeholder(\"Search some thing...\"));\n        let suffix_input1 = cx.new(|cx| {\n            InputState::new(window, cx)\n                .placeholder(\"This input only support [a-zA-Z0-9] characters.\")\n                .pattern(regex::Regex::new(r\"^[a-zA-Z0-9]*$\").unwrap())\n        });\n        let both_input1 = cx.new(|cx| {\n            InputState::new(window, cx).placeholder(\"This input have prefix and suffix.\")\n        });\n\n        let phone_input = cx.new(|cx| InputState::new(window, cx).mask_pattern(\"(999)-999-9999\"));\n        let mask_input2 = cx.new(|cx| InputState::new(window, cx).mask_pattern(\"AAA-###-AAA\"));\n        let currency_input = cx.new(|cx| {\n            InputState::new(window, cx).mask_pattern(MaskPattern::Number {\n                separator: Some(','),\n                fraction: Some(3),\n            })\n        });\n        let custom_input = cx.new(|cx| {\n            InputState::new(window, cx).placeholder(\"Custom Input use monospace, 0123456789.\")\n        });\n\n        let code_input = cx.new(|cx| {\n            InputState::new(window, cx)\n                .code_editor(\"json\")\n                .multi_line(false)\n                .show_whitespaces(true)\n                .default_value(CODE_EXAMPLE)\n        });\n\n        let input_text_centered = cx.new(|cx| {\n            InputState::new(window, cx)\n                .placeholder(\"Enter text to test center layout...\")\n                .default_value(\"Centered Text\")\n        });\n\n        let input_text_right = cx.new(|cx| {\n            InputState::new(window, cx)\n                .placeholder(\"Enter text to test right layout...\")\n                .default_value(\"Right Aligned Text\")\n        });\n\n        let _subscriptions = vec![\n            cx.subscribe_in(&input1, window, Self::on_input_event),\n            cx.subscribe_in(&input2, window, Self::on_input_event),\n            cx.subscribe_in(&phone_input, window, Self::on_input_event),\n        ];\n\n        Self {\n            input1,\n            input2,\n            input_esc,\n            mask_input,\n            disabled_input: cx\n                .new(|cx| InputState::new(window, cx).default_value(\"This is disabled input\")),\n            large_input: cx.new(|cx| InputState::new(window, cx).placeholder(\"Large input\")),\n            small_input: cx.new(|cx| {\n                InputState::new(window, cx)\n                    .validate(|s, _| s.parse::<f32>().is_ok())\n                    .placeholder(\"validate to limit float number.\")\n            }),\n            prefix_input1,\n            suffix_input1,\n            both_input1,\n            phone_input,\n            mask_input2,\n            currency_input,\n            custom_input,\n            code_input,\n            input_text_centered,\n            input_text_right,\n            _subscriptions,\n        }\n    }\n\n    fn on_input_event(\n        &mut self,\n        state: &Entity<InputState>,\n        event: &InputEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        match event {\n            InputEvent::Change => {\n                let text = state.read(cx).value();\n                if state == &self.input2 {\n                    println!(\"Set disabled value: {}\", text);\n                    self.disabled_input.update(cx, |this, cx| {\n                        this.set_value(text, window, cx);\n                    })\n                } else {\n                    println!(\"Change: {}\", text)\n                }\n            }\n            InputEvent::PressEnter { secondary } => println!(\"PressEnter secondary: {}\", secondary),\n            InputEvent::Focus => println!(\"Focus\"),\n            InputEvent::Blur => println!(\"Blur\"),\n        };\n    }\n\n    fn on_click_reset(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {\n        self.code_input.update(cx, |input_state, cx| {\n            input_state.set_value(CODE_EXAMPLE, window, cx);\n        });\n    }\n}\n\nimpl Render for InputStory {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .id(\"input-story\")\n            .size_full()\n            .justify_start()\n            .gap_3()\n            .child(\n                section(\"Normal Input\")\n                    .max_w_md()\n                    .child(Input::new(&self.input1).cleanable(true))\n                    .child(Input::new(&self.input2)),\n            )\n            .child(\n                section(\"Input State\")\n                    .max_w_md()\n                    .child(Input::new(&self.disabled_input).disabled(true))\n                    .child(Input::new(&self.mask_input).mask_toggle().cleanable(true)),\n            )\n            .child(\n                section(\"Text Align\").max_w_lg().child(\n                    h_flex()\n                        .w_full()\n                        .gap_4()\n                        .flex_wrap()\n                        .child(Input::new(&self.input_text_centered).text_center().flex_1())\n                        .child(Input::new(&self.input_text_right).text_right().flex_1()),\n                ),\n            )\n            .child(\n                section(\"Prefix and Suffix\")\n                    .max_w_md()\n                    .child(\n                        Input::new(&self.prefix_input1)\n                            .cleanable(true)\n                            .prefix(Icon::new(IconName::Search).small()),\n                    )\n                    .child(\n                        Input::new(&self.both_input1)\n                            .cleanable(true)\n                            .prefix(div().child(Icon::new(IconName::Search).small()))\n                            .suffix(Button::new(\"info\").ghost().icon(IconName::Info).xsmall()),\n                    )\n                    .child(\n                        Input::new(&self.suffix_input1)\n                            .cleanable(true)\n                            .suffix(Button::new(\"info\").ghost().icon(IconName::Info).xsmall()),\n                    ),\n            )\n            .child(\n                section(\"Currency Input with thousands separator\")\n                    .max_w_md()\n                    .child(Input::new(&self.currency_input))\n                    .child(\n                        div().child(format!(\"Value: {:?}\", self.currency_input.read(cx).value())),\n                    ),\n            )\n            .child(\n                section(\"Input with mask pattern: (999)-999-9999\")\n                    .max_w_md()\n                    .child(Input::new(&self.phone_input))\n                    .child(\n                        v_flex()\n                            .child(format!(\"Value: {:?}\", self.phone_input.read(cx).value()))\n                            .child(format!(\n                                \"Unmask Value: {:?}\",\n                                self.phone_input.read(cx).unmask_value()\n                            )),\n                    ),\n            )\n            .child(\n                section(\"Input with mask pattern: AAA-###-AAA\")\n                    .max_w_md()\n                    .child(Input::new(&self.mask_input2))\n                    .child(\n                        v_flex()\n                            .child(format!(\"Value: {:?}\", self.mask_input2.read(cx).value()))\n                            .child(format!(\n                                \"Unmask Value: {:?}\",\n                                self.mask_input2.read(cx).unmask_value()\n                            )),\n                    ),\n            )\n            .child(\n                section(\"Input Size\")\n                    .max_w_md()\n                    .child(Input::new(&self.large_input).large())\n                    .child(Input::new(&self.small_input).small()),\n            )\n            .child(\n                section(\"Cleanable and ESC to clean\")\n                    .max_w_md()\n                    .child(Input::new(&self.input_esc).cleanable(true)),\n            )\n            .child(\n                section(\"Focused Input\")\n                    .max_w_md()\n                    .whitespace_normal()\n                    .overflow_hidden()\n                    .child(div().child(format!(\n                        \"Value: {:?}\",\n                        window.focused_input(cx).map(|input| input.read(cx).value())\n                    ))),\n            )\n            .child(\n                section(\"Custom Appearance\").max_w_md().child(\n                    div()\n                        .border_b_2()\n                        .px_6()\n                        .py_3()\n                        .font_family(cx.theme().mono_font_family.clone())\n                        .border_color(cx.theme().border)\n                        .bg(cx.theme().secondary)\n                        .text_color(cx.theme().secondary_foreground)\n                        .w_full()\n                        .child(Input::new(&self.custom_input).appearance(false)),\n                ),\n            )\n            .child(\n                section(\"Single line code editor\").max_w_md().child(\n                    Input::new(&self.code_input).suffix(\n                        Button::new(\"code-reset\")\n                            .ghost()\n                            .label(\"Reset\")\n                            .xsmall()\n                            .on_click(cx.listener(Self::on_click_reset)),\n                    ),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/kbd_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, IntoElement, Keystroke, ParentElement, Render,\n    Styled, Window,\n};\n\nuse gpui_component::{h_flex, kbd::Kbd, v_flex};\n\nuse crate::section;\n\npub struct KbdStory {\n    focus_handle: gpui::FocusHandle,\n}\n\nimpl super::Story for KbdStory {\n    fn title() -> &'static str {\n        \"Kbd\"\n    }\n\n    fn description() -> &'static str {\n        \"A tag style to display keyboard shortcuts\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl KbdStory {\n    pub(crate) fn new(_: &mut Window, cx: &mut App) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\nimpl Focusable for KbdStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\nimpl Render for KbdStory {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_6()\n            .child(\n                section(\"Kbd\").child(\n                    h_flex()\n                        .gap_2()\n                        .child(Kbd::new(Keystroke::parse(\"cmd-shift-p\").unwrap()))\n                        .child(Kbd::new(Keystroke::parse(\"cmd-ctrl-t\").unwrap()))\n                        .child(Kbd::new(Keystroke::parse(\"cmd--\").unwrap()))\n                        .child(Kbd::new(Keystroke::parse(\"cmd-+\").unwrap()))\n                        .child(Kbd::new(Keystroke::parse(\"escape\").unwrap()))\n                        .child(Kbd::new(Keystroke::parse(\"backspace\").unwrap()))\n                        .child(Kbd::new(Keystroke::parse(\"/\").unwrap()))\n                        .child(Kbd::new(Keystroke::parse(\"enter\").unwrap())),\n                ),\n            )\n            .child(\n                section(\"Outline Style\").child(\n                    h_flex()\n                        .gap_2()\n                        .child(Kbd::new(Keystroke::parse(\"cmd-shift-p\").unwrap()).outline())\n                        .child(Kbd::new(Keystroke::parse(\"cmd-ctrl-t\").unwrap()).outline())\n                        .child(Kbd::new(Keystroke::parse(\"enter\").unwrap()).outline()),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/label_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, SharedString,\n    Styled, Subscription, Window, div, px, rems,\n};\n\nuse gpui_component::{\n    IconName, StyledExt,\n    button::{Button, ButtonVariant, ButtonVariants as _},\n    checkbox::Checkbox,\n    green_500, h_flex,\n    input::{Input, InputEvent, InputState},\n    label::{HighlightsMatch, Label},\n    v_flex,\n};\n\nuse crate::section;\n\npub struct LabelStory {\n    focus_handle: gpui::FocusHandle,\n    masked: bool,\n    highlights_text: SharedString,\n    highlights_input: Entity<InputState>,\n    prefix: bool,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl super::Story for LabelStory {\n    fn title() -> &'static str {\n        \"Label\"\n    }\n\n    fn description() -> &'static str {\n        \"Label used to display text or other content.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl LabelStory {\n    pub(crate) fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let highlights_input = cx.new(|cx| {\n            InputState::new(window, cx)\n                .placeholder(\"Enter text to highlight in the label\")\n                .clean_on_escape()\n        });\n\n        let _subscriptions =\n            vec![\n                cx.subscribe(&highlights_input, |this, state, e: &InputEvent, cx| {\n                    if let InputEvent::Change = e {\n                        this.highlights_text = state.read(cx).value();\n                        cx.notify();\n                    }\n                }),\n            ];\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            masked: false,\n            highlights_text: Default::default(),\n            highlights_input,\n            prefix: false,\n            _subscriptions,\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    #[allow(unused)]\n    fn on_click(checked: &bool, window: &mut Window, cx: &mut App) {\n        println!(\"Check value changed: {}\", checked);\n    }\n\n    fn highlights_text(&self) -> HighlightsMatch {\n        if self.prefix {\n            HighlightsMatch::Prefix(self.highlights_text.clone())\n        } else {\n            HighlightsMatch::Full(self.highlights_text.clone())\n        }\n    }\n}\nimpl Focusable for LabelStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\nimpl Render for LabelStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let ht = self.highlights_text();\n\n        v_flex()\n            .gap_6()\n            .child(\n                h_flex()\n                    .gap_x_3()\n                    .child(Input::new(&self.highlights_input).w_1_3())\n                    .child(\n                        Checkbox::new(\"prefix\")\n                            .label(\"Prefix\")\n                            .checked(self.prefix)\n                            .on_click(cx.listener(|view, _, _, cx| {\n                                view.prefix = !view.prefix;\n                                cx.notify();\n                            })),\n                    ),\n            )\n            .child(\n                section(\"Label\").max_w_md().items_start().child(\n                    v_flex()\n                        .gap_y_4()\n                        .child(Label::new(\"This is a label\").highlights(ht.clone()))\n                        // This case for test match CJK with ASCII, it was has a crash bug before.\n                        // Try to input \"AA\" to see the highlights effect.\n                        .child(Label::new(\"AAA中文BB\").highlights(ht.clone())),\n                ),\n            )\n            .child(\n                section(\"Label with secondary text\")\n                    .max_w_md()\n                    .items_start()\n                    .child(\n                        Label::new(\"Company Address\")\n                            .secondary(\"(optional)\")\n                            .highlights(ht.clone()),\n                    ),\n            )\n            .child(\n                section(\"Alignment\").max_w_md().child(\n                    v_flex()\n                        .w_full()\n                        .gap_4()\n                        .child(Label::new(\"Text align left\").highlights(ht.clone()))\n                        .child(\n                            Label::new(\"Text align center\")\n                                .text_center()\n                                .highlights(ht.clone()),\n                        )\n                        .child(\n                            Label::new(\"Text align right\")\n                                .text_right()\n                                .highlights(ht.clone()),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Label with color\").max_w_md().child(\n                    Label::new(\"Color Label\")\n                        .text_color(green_500())\n                        .highlights(ht.clone()),\n                ),\n            )\n            .child(\n                section(\"Font Size\").max_w_md().child(\n                    Label::new(\"Font Size Label\")\n                        .text_size(px(20.))\n                        .font_semibold()\n                        .line_height(rems(1.8))\n                        .highlights(ht.clone()),\n                ),\n            )\n            .child(\n                section(\"Multi-line, line-height and text wrap\")\n                    .max_w_md()\n                    .child(\n                        div().w(px(200.)).child(\n                            Label::new(\n                                \"Label should support text wrap in default, \\\n                                if the text is too long, it should wrap to the next line.\",\n                            )\n                            .line_height(rems(1.8))\n                            .highlights(ht.clone()),\n                        ),\n                    ),\n            )\n            .child(\n                section(\"Masked Label\").max_w_md().child(\n                    v_flex()\n                        .w_full()\n                        .gap_4()\n                        .child(\n                            h_flex()\n                                .child(\n                                    Label::new(\"9,182,1 USD\")\n                                        .text_2xl()\n                                        .masked(self.masked)\n                                        .highlights(ht.clone()),\n                                )\n                                .child(\n                                    Button::new(\"btn-mask\")\n                                        .with_variant(ButtonVariant::Ghost)\n                                        .icon(if self.masked {\n                                            IconName::EyeOff\n                                        } else {\n                                            IconName::Eye\n                                        })\n                                        .on_click(cx.listener(|this, _, _, _| {\n                                            this.masked = !this.masked;\n                                        })),\n                                ),\n                        )\n                        .child(\n                            Label::new(\"500 USD\")\n                                .text_xl()\n                                .masked(self.masked)\n                                .highlights(ht.clone()),\n                        ),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/list_story.rs",
    "content": "use std::{rc::Rc, time::Duration};\n\nuse fake::Fake;\nuse gpui::{\n    App, AppContext, Context, ElementId, Entity, FocusHandle, Focusable, InteractiveElement,\n    IntoElement, ParentElement, Render, RenderOnce, ScrollStrategy, SharedString, Styled,\n    Subscription, Task, Window, actions, div, px,\n};\n\nuse gpui_component::{\n    ActiveTheme, Icon, IconName, IndexPath, Selectable, Sizable,\n    button::Button,\n    checkbox::Checkbox,\n    h_flex,\n    label::Label,\n    list::{List, ListDelegate, ListEvent, ListItem, ListState},\n    v_flex,\n};\n\nactions!(list_story, [SelectedCompany]);\n\n#[derive(Clone, Default)]\nstruct Company {\n    name: SharedString,\n    industry: SharedString,\n    last_done: f64,\n    prev_close: f64,\n\n    change_percent: f64,\n    change_percent_str: SharedString,\n    last_done_str: SharedString,\n    prev_close_str: SharedString,\n    // description: String,\n}\n\nimpl Company {\n    fn prepare(mut self) -> Self {\n        self.change_percent = (self.last_done - self.prev_close) / self.prev_close;\n        self.change_percent_str = format!(\"{:.2}%\", self.change_percent).into();\n        self.last_done_str = format!(\"{:.2}\", self.last_done).into();\n        self.prev_close_str = format!(\"{:.2}\", self.prev_close).into();\n        self\n    }\n}\n\n#[derive(IntoElement)]\nstruct CompanyListItem {\n    base: ListItem,\n    company: Rc<Company>,\n    selected: bool,\n}\n\nimpl CompanyListItem {\n    pub fn new(id: impl Into<ElementId>, company: Rc<Company>, selected: bool) -> Self {\n        CompanyListItem {\n            company,\n            base: ListItem::new(id).selected(selected),\n            selected,\n        }\n    }\n}\n\nimpl Selectable for CompanyListItem {\n    fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        self.selected\n    }\n}\n\nimpl RenderOnce for CompanyListItem {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let text_color = if self.selected {\n            cx.theme().accent_foreground\n        } else {\n            cx.theme().foreground\n        };\n\n        let trend_color = match self.company.change_percent {\n            change if change > 0.0 => cx.theme().green,\n            change if change < 0.0 => cx.theme().red,\n            _ => cx.theme().foreground,\n        };\n\n        self.base\n            .px_2()\n            .py_1()\n            .overflow_x_hidden()\n            .border_1()\n            .rounded(cx.theme().radius)\n            .child(\n                h_flex()\n                    .items_center()\n                    .justify_between()\n                    .gap_2()\n                    .text_color(text_color)\n                    .child(\n                        h_flex().gap_2().child(\n                            v_flex()\n                                .gap_1()\n                                .max_w(px(500.))\n                                .overflow_x_hidden()\n                                .flex_nowrap()\n                                .child(Label::new(self.company.name.clone()).whitespace_nowrap()),\n                        ),\n                    )\n                    .child(\n                        h_flex()\n                            .gap_2()\n                            .items_center()\n                            .justify_end()\n                            .child(\n                                div()\n                                    .w(px(65.))\n                                    .text_color(text_color)\n                                    .child(self.company.last_done_str.clone()),\n                            )\n                            .child(\n                                h_flex().w(px(65.)).justify_end().child(\n                                    div()\n                                        .rounded(cx.theme().radius)\n                                        .whitespace_nowrap()\n                                        .text_size(px(12.))\n                                        .px_1()\n                                        .text_color(trend_color)\n                                        .child(self.company.change_percent_str.clone()),\n                                ),\n                            ),\n                    ),\n            )\n    }\n}\n\nstruct CompanyListDelegate {\n    industries: Vec<SharedString>,\n    _companies: Vec<Rc<Company>>,\n    matched_companies: Vec<Vec<Rc<Company>>>,\n    selected_index: Option<IndexPath>,\n    confirmed_index: Option<IndexPath>,\n    query: SharedString,\n    loading: bool,\n    eof: bool,\n    lazy_load: bool,\n}\n\nimpl CompanyListDelegate {\n    fn prepare(&mut self, query: impl Into<SharedString>) {\n        self.query = query.into();\n        let companies: Vec<Rc<Company>> = self\n            ._companies\n            .iter()\n            .filter(|company| {\n                company\n                    .name\n                    .to_lowercase()\n                    .contains(&self.query.to_lowercase())\n            })\n            .cloned()\n            .collect();\n        for company in companies.into_iter() {\n            if let Some(ix) = self.industries.iter().position(|s| s == &company.industry) {\n                self.matched_companies[ix].push(company);\n            } else {\n                self.industries.push(company.industry.clone());\n                self.matched_companies.push(vec![company]);\n            }\n        }\n    }\n\n    fn extend_more(&mut self, len: usize) {\n        self._companies\n            .extend((0..len).map(|_| Rc::new(random_company())));\n        self.prepare(self.query.clone());\n    }\n\n    fn selected_company(&self) -> Option<Rc<Company>> {\n        let Some(ix) = self.selected_index else {\n            return None;\n        };\n\n        self.matched_companies\n            .get(ix.section)\n            .and_then(|c| c.get(ix.row))\n            .cloned()\n    }\n}\n\nimpl ListDelegate for CompanyListDelegate {\n    type Item = CompanyListItem;\n\n    fn sections_count(&self, _: &App) -> usize {\n        self.industries.len()\n    }\n\n    fn items_count(&self, section: usize, _: &App) -> usize {\n        if matches!(section, 0 | 2 | 3) {\n            // Return some empty sections for testing.\n            return 0;\n        }\n\n        self.matched_companies[section].len()\n    }\n\n    fn perform_search(\n        &mut self,\n        query: &str,\n        _: &mut Window,\n        _: &mut Context<ListState<Self>>,\n    ) -> Task<()> {\n        self.prepare(query.to_owned());\n        Task::ready(())\n    }\n\n    fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<ListState<Self>>) {\n        println!(\"Confirmed with secondary: {}\", secondary);\n        window.dispatch_action(Box::new(SelectedCompany), cx);\n    }\n\n    fn set_selected_index(\n        &mut self,\n        ix: Option<IndexPath>,\n        _: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) {\n        self.selected_index = ix;\n        cx.notify();\n    }\n\n    fn set_right_clicked_index(\n        &mut self,\n        ix: Option<IndexPath>,\n        _: &mut Window,\n        _: &mut Context<ListState<Self>>,\n    ) {\n        println!(\"right_clicked_index: {:?}\", ix);\n    }\n\n    fn render_section_header(\n        &mut self,\n        section: usize,\n        _: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> Option<impl IntoElement> {\n        let Some(industry) = self.industries.get(section) else {\n            return None;\n        };\n\n        Some(\n            h_flex()\n                .pb_1()\n                .px_2()\n                .gap_2()\n                .text_sm()\n                .text_color(cx.theme().muted_foreground)\n                .child(Icon::new(IconName::Folder))\n                .child(industry.clone())\n                .child(format!(\"(section: {})\", section)),\n        )\n    }\n\n    fn render_section_footer(\n        &mut self,\n        section: usize,\n        _: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> Option<impl IntoElement> {\n        let Some(_) = self.industries.get(section) else {\n            return None;\n        };\n\n        Some(\n            div()\n                .pt_1()\n                .pb_5()\n                .px_2()\n                .text_xs()\n                .text_color(cx.theme().muted_foreground)\n                .child(format!(\n                    \"Total {} items in section.\",\n                    self.matched_companies[section].len()\n                )),\n        )\n    }\n\n    fn render_item(\n        &mut self,\n        ix: IndexPath,\n        _: &mut Window,\n        _: &mut Context<ListState<Self>>,\n    ) -> Option<Self::Item> {\n        let selected = Some(ix) == self.selected_index || Some(ix) == self.confirmed_index;\n        if let Some(company) = self.matched_companies[ix.section].get(ix.row) {\n            return Some(CompanyListItem::new(ix, company.clone(), selected));\n        }\n\n        None\n    }\n\n    fn loading(&self, _: &App) -> bool {\n        self.loading\n    }\n\n    fn has_more(&self, _: &App) -> bool {\n        if self.loading {\n            return false;\n        }\n\n        return !self.eof;\n    }\n\n    fn load_more_threshold(&self) -> usize {\n        150\n    }\n\n    fn load_more(&mut self, window: &mut Window, cx: &mut Context<ListState<Self>>) {\n        if !self.lazy_load {\n            return;\n        }\n\n        cx.spawn_in(window, async move |view, window| {\n            // Simulate network request, delay 1s to load data.\n            window\n                .background_executor()\n                .timer(Duration::from_secs(1))\n                .await;\n\n            _ = view.update_in(window, move |view, window, cx| {\n                let query = view.delegate().query.clone();\n                view.delegate_mut().extend_more(200);\n                _ = view.delegate_mut().perform_search(&query, window, cx);\n                view.delegate_mut().eof = view.delegate()._companies.len() >= 6000;\n            });\n        })\n        .detach();\n    }\n}\n\npub struct ListStory {\n    focus_handle: FocusHandle,\n    company_list: Entity<ListState<CompanyListDelegate>>,\n    selected_company: Option<Rc<Company>>,\n    selectable: bool,\n    searchable: bool,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl super::Story for ListStory {\n    fn title() -> &'static str {\n        \"List\"\n    }\n\n    fn description() -> &'static str {\n        \"A list displays a series of items.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl ListStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let mut delegate = CompanyListDelegate {\n            industries: vec![],\n            matched_companies: vec![vec![]],\n            _companies: vec![],\n            selected_index: Some(IndexPath::default()),\n            confirmed_index: None,\n            query: \"\".into(),\n            loading: false,\n            eof: false,\n            lazy_load: false,\n        };\n        delegate.extend_more(100);\n\n        let company_list = cx.new(|cx| ListState::new(delegate, window, cx).searchable(true));\n\n        let _subscriptions =\n            vec![\n                cx.subscribe(&company_list, |_, _, ev: &ListEvent, _| match ev {\n                    ListEvent::Select(ix) => {\n                        println!(\"List Selected: {:?}\", ix);\n                    }\n                    ListEvent::Confirm(ix) => {\n                        println!(\"List Confirmed: {:?}\", ix);\n                    }\n                    ListEvent::Cancel => {\n                        println!(\"List Cancelled\");\n                    }\n                }),\n            ];\n\n        // Spawn a background to random refresh the list\n        cx.spawn(async move |this, cx| {\n            this.update(cx, |this, cx| {\n                this.company_list.update(cx, |picker, _| {\n                    picker\n                        .delegate_mut()\n                        ._companies\n                        .iter_mut()\n                        .for_each(|company| {\n                            let mut new_company = random_company();\n                            new_company.name = company.name.clone();\n                            new_company.industry = company.industry.clone();\n                            *company = Rc::new(new_company);\n                        });\n                    picker.delegate_mut().prepare(\"\");\n                });\n                cx.notify();\n            })\n            .ok();\n        })\n        .detach();\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            searchable: true,\n            selectable: true,\n            company_list,\n            selected_company: None,\n            _subscriptions,\n        }\n    }\n\n    fn selected_company(&mut self, _: &SelectedCompany, _: &mut Window, cx: &mut Context<Self>) {\n        let picker = self.company_list.read(cx);\n        if let Some(company) = picker.delegate().selected_company() {\n            self.selected_company = Some(company);\n        }\n    }\n\n    fn toggle_selectable(&mut self, selectable: bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.selectable = selectable;\n        self.company_list.update(cx, |list, cx| {\n            list.set_selectable(self.selectable, cx);\n        })\n    }\n\n    fn toggle_searchable(&mut self, searchable: bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.searchable = searchable;\n        self.company_list.update(cx, |list, cx| {\n            list.set_searchable(self.searchable, cx);\n        })\n    }\n}\n\nfn random_company() -> Company {\n    let last_done = (0.0..999.0).fake::<f64>();\n    let prev_close = last_done * (-0.1..0.1).fake::<f64>();\n\n    Company {\n        name: fake::faker::company::en::CompanyName()\n            .fake::<String>()\n            .into(),\n        industry: fake::faker::company::en::Industry().fake::<String>().into(),\n        last_done,\n        prev_close,\n        ..Default::default()\n    }\n    .prepare()\n}\n\nimpl Focusable for ListStory {\n    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for ListStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let lazy_load = self.company_list.read(cx).delegate().lazy_load;\n\n        v_flex()\n            .track_focus(&self.focus_handle)\n            .on_action(cx.listener(Self::selected_company))\n            .size_full()\n            .gap_4()\n            .child(\n                h_flex()\n                    .gap_2()\n                    .child(\n                        Button::new(\"scroll-top\")\n                            .outline()\n                            .child(\"Scroll to Top\")\n                            .small()\n                            .on_click(cx.listener(|this, _, window, cx| {\n                                this.company_list.update(cx, |list, cx| {\n                                    list.scroll_to_item(\n                                        IndexPath::default(),\n                                        ScrollStrategy::Top,\n                                        window,\n                                        cx,\n                                    );\n                                    cx.notify();\n                                })\n                            })),\n                    )\n                    .child(\n                        Button::new(\"scroll-selected\")\n                            .outline()\n                            .child(\"Scroll to selected\")\n                            .small()\n                            .on_click(cx.listener(|this, _, window, cx| {\n                                this.company_list.update(cx, |list, cx| {\n                                    list.scroll_to_selected_item(window, cx);\n                                })\n                            })),\n                    )\n                    .child(\n                        Button::new(\"scroll-to-item\")\n                            .outline()\n                            .child(\"Scroll to (5, 1)\")\n                            .small()\n                            .on_click(cx.listener(|this, _, window, cx| {\n                                this.company_list.update(cx, |list, cx| {\n                                    list.scroll_to_item(\n                                        IndexPath::new(1).section(5),\n                                        ScrollStrategy::Center,\n                                        window,\n                                        cx,\n                                    );\n                                })\n                            })),\n                    )\n                    .child(\n                        Button::new(\"scroll-bottom\")\n                            .outline()\n                            .child(\"Scroll to Bottom\")\n                            .small()\n                            .on_click(cx.listener(|this, _, window, cx| {\n                                this.company_list.update(cx, |list, cx| {\n                                    let last_section =\n                                        list.delegate().sections_count(cx).saturating_sub(1);\n\n                                    list.scroll_to_item(\n                                        IndexPath::default().section(last_section).row(\n                                            list.delegate()\n                                                .items_count(last_section, cx)\n                                                .saturating_sub(1),\n                                        ),\n                                        ScrollStrategy::Top,\n                                        window,\n                                        cx,\n                                    );\n                                })\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"selectable\")\n                            .label(\"Selectable\")\n                            .checked(self.selectable)\n                            .on_click(cx.listener(|this, check: &bool, window, cx| {\n                                this.toggle_selectable(*check, window, cx)\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"searchable\")\n                            .label(\"Searchable\")\n                            .checked(self.searchable)\n                            .on_click(cx.listener(|this, check: &bool, window, cx| {\n                                this.toggle_searchable(*check, window, cx)\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"loading\")\n                            .label(\"Loading\")\n                            .checked(self.company_list.read(cx).delegate().loading)\n                            .on_click(cx.listener(|this, check: &bool, _, cx| {\n                                this.company_list.update(cx, |this, cx| {\n                                    this.delegate_mut().loading = *check;\n                                    cx.notify();\n                                })\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"lazy_load\")\n                            .label(\"Lazy Load\")\n                            .checked(lazy_load)\n                            .on_click(cx.listener(|this, check: &bool, _, cx| {\n                                this.company_list.update(cx, |this, cx| {\n                                    this.delegate_mut().lazy_load = *check;\n                                    cx.notify();\n                                })\n                            })),\n                    ),\n            )\n            .child(\n                List::new(&self.company_list)\n                    .p(px(8.))\n                    .flex_1()\n                    .w_full()\n                    .border_1()\n                    .border_color(cx.theme().border)\n                    .rounded(cx.theme().radius),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/menu_story.rs",
    "content": "use gpui::{\n    Action, App, AppContext, Context, Corner, Entity, InteractiveElement, IntoElement, KeyBinding,\n    ParentElement as _, Render, SharedString, Styled as _, Window, actions, div, px,\n};\nuse gpui_component::{\n    ActiveTheme as _, IconName, Side, StyledExt,\n    button::Button,\n    h_flex,\n    menu::{ContextMenuExt, DropdownMenu as _, PopupMenuItem},\n    v_flex,\n};\nuse serde::Deserialize;\n\nuse crate::section;\n\n#[derive(Action, Clone, PartialEq, Deserialize)]\n#[action(namespace = menu_story, no_json)]\nstruct Info(usize);\n\nactions!(menu_story, [Copy, Paste, Cut, SearchAll, ToggleCheck]);\n\nconst CONTEXT: &str = \"menu_story\";\npub fn init(cx: &mut App) {\n    cx.bind_keys([\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-c\", Copy, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-c\", Copy, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-v\", Paste, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-v\", Paste, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-x\", Cut, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-x\", Cut, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-shift-f\", SearchAll, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-shift-f\", SearchAll, Some(CONTEXT)),\n        KeyBinding::new(\"ctrl-shift-alt-t\", ToggleCheck, Some(CONTEXT)),\n    ])\n}\n\npub struct MenuStory {\n    check_side: Option<Side>,\n    message: String,\n}\n\nimpl super::Story for MenuStory {\n    fn title() -> &'static str {\n        \"Menu\"\n    }\n\n    fn description() -> &'static str {\n        \"Popup menu and context menu\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl MenuStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, _: &mut Context<Self>) -> Self {\n        Self {\n            check_side: None,\n            message: \"\".to_string(),\n        }\n    }\n\n    fn on_copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {\n        self.message = \"You have clicked copy\".to_string();\n        cx.notify()\n    }\n\n    fn on_cut(&mut self, _: &Cut, _: &mut Window, cx: &mut Context<Self>) {\n        self.message = \"You have clicked cut\".to_string();\n        cx.notify()\n    }\n\n    fn on_paste(&mut self, _: &Paste, _: &mut Window, cx: &mut Context<Self>) {\n        self.message = \"You have clicked paste\".to_string();\n        cx.notify()\n    }\n\n    fn on_search_all(&mut self, _: &SearchAll, _: &mut Window, cx: &mut Context<Self>) {\n        self.message = \"You have clicked search all\".to_string();\n        cx.notify()\n    }\n\n    fn on_action_info(&mut self, info: &Info, _: &mut Window, cx: &mut Context<Self>) {\n        self.message = format!(\"You have clicked info: {}\", info.0);\n        cx.notify()\n    }\n\n    fn on_action_toggle_check(&mut self, _: &ToggleCheck, _: &mut Window, cx: &mut Context<Self>) {\n        self.check_side = if self.check_side == Some(Side::Left) {\n            Some(Side::Right)\n        } else if self.check_side == Some(Side::Right) {\n            None\n        } else {\n            Some(Side::Left)\n        };\n\n        self.message = format!(\"You have used check at side: {:?}\", self.check_side);\n        cx.notify()\n    }\n}\n\nimpl Render for MenuStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let check_side = self.check_side;\n        let view = cx.entity();\n\n        v_flex()\n            .key_context(CONTEXT)\n            .on_action(cx.listener(Self::on_copy))\n            .on_action(cx.listener(Self::on_cut))\n            .on_action(cx.listener(Self::on_paste))\n            .on_action(cx.listener(Self::on_search_all))\n            .on_action(cx.listener(Self::on_action_info))\n            .on_action(cx.listener(Self::on_action_toggle_check))\n            .size_full()\n            .min_h(px(400.))\n            .gap_6()\n            .child(\n                section(\"Popup Menu\")\n                    .child(\n                        Button::new(\"popup-menu-1\")\n                            .outline()\n                            .label(\"Edit\")\n                            .dropdown_menu(move |this, window, cx| {\n                                this.link(\"About\", \"https://github.com/longbridge/gpui-component\")\n                                    .check_side(check_side.unwrap_or(Side::Left))\n                                    .separator()\n                                    .item(PopupMenuItem::new(\"Handle Click\").on_click(\n                                        window.listener_for(&view, |this, _, _, cx| {\n                                            this.message =\n                                                \"You have clicked Handle Click\".to_string();\n                                            cx.notify();\n                                        }),\n                                    ))\n                                    .separator()\n                                    .menu(\"Copy\", Box::new(Copy))\n                                    .menu(\"Cut\", Box::new(Cut))\n                                    .menu(\"Paste\", Box::new(Paste))\n                                    .separator()\n                                    .menu_with_check(\n                                        format!(\"Check Side {:?}\", check_side),\n                                        check_side.is_some(),\n                                        Box::new(ToggleCheck),\n                                    )\n                                    .separator()\n                                    .menu_with_icon(\"Search\", IconName::Search, Box::new(SearchAll))\n                                    .separator()\n                                    .item(\n                                        PopupMenuItem::element(|_, cx| {\n                                            v_flex().child(\"Custom Element\").child(\n                                                div()\n                                                    .text_xs()\n                                                    .text_color(cx.theme().muted_foreground)\n                                                    .child(\"This is sub-title\"),\n                                            )\n                                        })\n                                        .on_click(\n                                            window.listener_for(&view, |this, _, _, cx| {\n                                                this.message = \"You have clicked on custom element\"\n                                                    .to_string();\n                                                cx.notify();\n                                            }),\n                                        ),\n                                    )\n                                    .menu_element_with_check(\n                                        check_side.is_some(),\n                                        Box::new(ToggleCheck),\n                                        |_, cx| {\n                                            h_flex().gap_1().child(\"Custom Element\").child(\n                                                div()\n                                                    .text_xs()\n                                                    .text_color(cx.theme().muted_foreground)\n                                                    .child(\"checked\"),\n                                            )\n                                        },\n                                    )\n                                    .menu_element_with_icon(\n                                        IconName::Info,\n                                        Box::new(Info(0)),\n                                        |_, cx| {\n                                            h_flex().gap_1().child(\"Custom\").child(\n                                                div()\n                                                    .text_sm()\n                                                    .text_color(cx.theme().muted_foreground)\n                                                    .child(\"element\"),\n                                            )\n                                        },\n                                    )\n                                    .separator()\n                                    .menu_with_disabled(\"Disabled Item\", Box::new(Info(0)), true)\n                                    .separator()\n                                    .submenu(\"Links\", window, cx, |menu, _, _| {\n                                        menu.link_with_icon(\n                                            \"GPUI Component\",\n                                            IconName::Github,\n                                            \"https://github.com/longbridge/gpui-component\",\n                                        )\n                                        .separator()\n                                        .link(\"GPUI\", \"https://gpui.rs\")\n                                        .link(\"Zed\", \"https://zed.dev\")\n                                    })\n                                    .separator()\n                                    .submenu(\"Other Links\", window, cx, |menu, _, _| {\n                                        menu.link(\"Crates\", \"https://crates.io\")\n                                            .link(\"Rust Docs\", \"https://docs.rs\")\n                                    })\n                            }),\n                    )\n                    .child(self.message.clone()),\n            )\n            .child(\n                section(\"Context Menu\")\n                    .v_flex()\n                    .gap_4()\n                    .child(\n                        v_flex()\n                            .w_full()\n                            .p_4()\n                            .items_center()\n                            .justify_center()\n                            .min_h_20()\n                            .rounded(cx.theme().radius_lg)\n                            .border_2()\n                            .border_dashed()\n                            .border_color(cx.theme().border)\n                            .child(\"Right click to open ContextMenu\")\n                            .context_menu({\n                                move |this, window, cx| {\n                                    this.check_side(check_side.unwrap_or(Side::Left))\n                                        .external_link_icon(false)\n                                        .link(\n                                            \"About\",\n                                            \"https://github.com/longbridge/gpui-component\",\n                                        )\n                                        .separator()\n                                        .menu(\"Cut\", Box::new(Cut))\n                                        .menu(\"Copy\", Box::new(Copy))\n                                        .menu(\"Paste\", Box::new(Paste))\n                                        .separator()\n                                        .label(\"This is a label\")\n                                        .menu_with_check(\n                                            format!(\"Check Side {:?}\", check_side),\n                                            check_side.is_some(),\n                                            Box::new(ToggleCheck),\n                                        )\n                                        .separator()\n                                        .submenu(\"Settings\", window, cx, move |menu, _, _| {\n                                            menu.menu(\"Info 0\", Box::new(Info(0)))\n                                                .separator()\n                                                .menu(\"Item 1\", Box::new(Info(1)))\n                                                .menu(\"Item 2\", Box::new(Info(2)))\n                                        })\n                                        .separator()\n                                        .menu(\"Search All\", Box::new(SearchAll))\n                                        .separator()\n                                }\n                            })\n                            .child(\n                                div()\n                                    .text_sm()\n                                    .text_color(cx.theme().muted_foreground)\n                                    .child(\n                                        \"You can right click anywhere in \\\n                                         this area to open the context menu.\",\n                                    ),\n                            ),\n                    )\n                    .child(\n                        div()\n                            .id(\"other\")\n                            .flex()\n                            .w_full()\n                            .p_4()\n                            .items_center()\n                            .justify_center()\n                            .min_h_20()\n                            .rounded(cx.theme().radius_lg)\n                            .border_2()\n                            .border_dashed()\n                            .border_color(cx.theme().border)\n                            .child(\"Here is another area with context menu.\")\n                            .context_menu({\n                                move |this, _, _| {\n                                    this.link(\n                                        \"About\",\n                                        \"https://github.com/longbridge/gpui-component\",\n                                    )\n                                    .separator()\n                                    .menu(\"Item 1\", Box::new(Info(1)))\n                                }\n                            }),\n                    )\n                    .child(\n                        div()\n                            .id(\"other1\")\n                            .flex()\n                            .w_full()\n                            .p_4()\n                            .items_center()\n                            .justify_center()\n                            .min_h_20()\n                            .rounded(cx.theme().radius_lg)\n                            .border_2()\n                            .border_dashed()\n                            .border_color(cx.theme().border)\n                            .child(\"ContextMenu area 1\")\n                            .context_menu({\n                                move |this, _, _| {\n                                    this.link(\n                                        \"About\",\n                                        \"https://github.com/longbridge/gpui-component\",\n                                    )\n                                    .separator()\n                                    .menu(\"Item 1\", Box::new(Info(1)))\n                                }\n                            }),\n                    ),\n            )\n            .child(\n                section(\"Menu with scrollbar\")\n                    .child(\n                        Button::new(\"dropdown-menu-scrollable-1\")\n                            .outline()\n                            .label(\"Scrollable Menu (100 items)\")\n                            .dropdown_menu_with_anchor(Corner::TopRight, move |this, _, _| {\n                                let mut this = this\n                                    .scrollable(true)\n                                    .max_h(px(300.))\n                                    .label(format!(\"Total {} items\", 100));\n                                for i in 0..100 {\n                                    if i % 5 == 0 {\n                                        this = this.separator();\n                                    }\n\n                                    this = this.menu(\n                                        SharedString::from(format!(\"Item {}\", i)),\n                                        Box::new(Info(i)),\n                                    )\n                                }\n                                this.min_w(px(100.))\n                            }),\n                    )\n                    .child(\n                        Button::new(\"dropdown-menu-scrollable-2\")\n                            .outline()\n                            .label(\"Scrollable Menu (5 items)\")\n                            .dropdown_menu_with_anchor(Corner::TopRight, move |this, _, _| {\n                                let mut this = this\n                                    .scrollable(true)\n                                    .max_h(px(300.))\n                                    .label(format!(\"Total {} items\", 100));\n                                for i in 0..5 {\n                                    this = this.menu(\n                                        SharedString::from(format!(\"Item {}\", i)),\n                                        Box::new(Info(i)),\n                                    )\n                                }\n                                this.min_w(px(100.))\n                            }),\n                    ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/mod.rs",
    "content": "use gpui::{AnyView, App, AppContext as _, Entity, Hsla, Pixels, Render, Window, px};\nuse gpui_component::dock::PanelControl;\n\nmod accordion_story;\nmod alert_dialog_story;\nmod alert_story;\nmod avatar_story;\nmod badge_story;\nmod breadcrumb_story;\nmod button_story;\nmod calendar_story;\nmod chart_story;\nmod checkbox_story;\nmod clipboard_story;\nmod collapsible_story;\nmod color_picker_story;\nmod data_table_story;\nmod date_picker_story;\nmod description_list_story;\nmod dialog_story;\nmod divider_story;\nmod dropdown_button_story;\nmod editor_story;\nmod form_story;\nmod group_box_story;\nmod hover_card_story;\nmod icon_story;\nmod image_story;\nmod input_story;\nmod kbd_story;\nmod label_story;\nmod list_story;\nmod menu_story;\nmod notification_story;\nmod number_input_story;\nmod otp_input_story;\nmod pagination_story;\nmod popover_story;\nmod progress_story;\nmod radio_story;\nmod rating_story;\nmod resizable_story;\nmod scrollbar_story;\nmod select_story;\nmod settings_story;\nmod sheet_story;\nmod sidebar_story;\nmod skeleton_story;\nmod slider_story;\nmod spinner_story;\nmod stepper_story;\nmod switch_story;\nmod table_story;\nmod tabs_story;\nmod tag_story;\nmod textarea_story;\nmod theme_story;\nmod toggle_story;\nmod tooltip_story;\nmod tree_story;\nmod virtual_list_story;\nmod welcome_story;\n\npub use accordion_story::AccordionStory;\npub use alert_dialog_story::AlertDialogStory;\npub use alert_story::AlertStory;\npub use avatar_story::AvatarStory;\npub use badge_story::BadgeStory;\npub use breadcrumb_story::BreadcrumbStory;\npub use button_story::ButtonStory;\npub use calendar_story::CalendarStory;\npub use chart_story::ChartStory;\npub use checkbox_story::CheckboxStory;\npub use clipboard_story::ClipboardStory;\npub use collapsible_story::CollapsibleStory;\npub use color_picker_story::ColorPickerStory;\npub use data_table_story::DataTableStory;\npub use date_picker_story::DatePickerStory;\npub use description_list_story::DescriptionListStory;\npub use dialog_story::DialogStory;\npub use divider_story::DividerStory;\npub use dropdown_button_story::DropdownButtonStory;\npub use editor_story::EditorStory;\npub use form_story::FormStory;\npub use group_box_story::GroupBoxStory;\npub use hover_card_story::HoverCardStory;\npub use icon_story::IconStory;\npub use image_story::ImageStory;\npub use input_story::InputStory;\npub use kbd_story::KbdStory;\npub use label_story::LabelStory;\npub use list_story::ListStory;\npub use menu_story::MenuStory;\npub use notification_story::NotificationStory;\npub use number_input_story::NumberInputStory;\npub use otp_input_story::OtpInputStory;\npub use pagination_story::PaginationStory;\npub use popover_story::PopoverStory;\npub use progress_story::ProgressStory;\npub use radio_story::RadioStory;\npub use rating_story::RatingStory;\npub use resizable_story::ResizableStory;\npub use scrollbar_story::ScrollbarStory;\npub use select_story::SelectStory;\npub use settings_story::SettingsStory;\npub use sheet_story::SheetStory;\npub use sidebar_story::SidebarStory;\npub use skeleton_story::SkeletonStory;\npub use slider_story::SliderStory;\npub use spinner_story::SpinnerStory;\npub use stepper_story::StepperStory;\npub use switch_story::SwitchStory;\npub use table_story::TableStory;\npub use tabs_story::TabsStory;\npub use tag_story::TagStory;\npub use textarea_story::TextareaStory;\npub use theme_story::ThemeColorsStory;\npub use toggle_story::ToggleStory;\npub use tooltip_story::TooltipStory;\npub use tree_story::TreeStory;\npub use virtual_list_story::VirtualListStory;\n\npub use welcome_story::WelcomeStory;\n\npub(crate) fn init(cx: &mut App) {\n    input_story::init(cx);\n    rating_story::init(cx);\n    number_input_story::init(cx);\n    textarea_story::init(cx);\n    select_story::init(cx);\n    popover_story::init(cx);\n    menu_story::init(cx);\n    tooltip_story::init(cx);\n    otp_input_story::init(cx);\n    tree_story::init(cx);\n}\n\npub trait Story: Render + Sized {\n    fn klass() -> &'static str {\n        std::any::type_name::<Self>().split(\"::\").last().unwrap()\n    }\n\n    fn title() -> &'static str;\n\n    fn description() -> &'static str {\n        \"\"\n    }\n\n    fn closable() -> bool {\n        true\n    }\n\n    fn zoomable() -> Option<PanelControl> {\n        Some(PanelControl::default())\n    }\n\n    fn title_bg() -> Option<Hsla> {\n        None\n    }\n\n    fn paddings() -> Pixels {\n        px(16.)\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render>;\n\n    fn on_active(&mut self, active: bool, window: &mut Window, cx: &mut App) {\n        let _ = active;\n        let _ = window;\n        let _ = cx;\n    }\n\n    fn on_active_any(view: AnyView, active: bool, window: &mut Window, cx: &mut App)\n    where\n        Self: 'static,\n    {\n        if let Some(story) = view.downcast::<Self>().ok() {\n            cx.update_entity(&story, |story, cx| {\n                story.on_active(active, window, cx);\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/notification_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, InteractiveElement as _, IntoElement,\n    ParentElement, Render, Styled, Window,\n};\n\nuse gpui_component::{\n    ActiveTheme, Anchor, Theme, WindowExt as _,\n    button::{Button, ButtonVariants},\n    h_flex,\n    menu::{DropdownMenu as _, PopupMenuItem},\n    notification::{Notification, NotificationType},\n    text::markdown,\n    v_flex,\n};\n\nuse crate::section;\n\nconst NOTIFICATION_MARKDOWN: &str = r#\"\nThis is a custom notification.\n- List item 1\n- List item 2\n- [Click here](https://github.com/longbridge/gpui-component)\n\"#;\n\npub struct NotificationStory {\n    focus_handle: FocusHandle,\n}\n\nimpl super::Story for NotificationStory {\n    fn title() -> &'static str {\n        \"Notification\"\n    }\n\n    fn description() -> &'static str {\n        \"Push notifications to display a message at the top right of the window\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl NotificationStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n}\n\nimpl Focusable for NotificationStory {\n    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for NotificationStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        const ANCHORS: [Anchor; 6] = [\n            Anchor::TopLeft,\n            Anchor::TopCenter,\n            Anchor::TopRight,\n            Anchor::BottomLeft,\n            Anchor::BottomCenter,\n            Anchor::BottomRight,\n        ];\n\n        let view = cx.entity();\n\n        v_flex()\n            .id(\"notification-story\")\n            .track_focus(&self.focus_handle)\n            .size_full()\n            .gap_3()\n            .child(\n                h_flex().gap_3().child(\n                    Button::new(\"placement\")\n                        .outline()\n                        .label(cx.theme().notification.placement.to_string())\n                        .dropdown_menu(move |menu, window, cx| {\n                            let menu = ANCHORS.into_iter().fold(menu, |menu, placement| {\n                                menu.item(\n                                    PopupMenuItem::new(placement.to_string())\n                                        .checked(cx.theme().notification.placement == placement)\n                                        .on_click(window.listener_for(\n                                            &view,\n                                            move |_, _, _, cx| {\n                                                Theme::global_mut(cx).notification.placement =\n                                                    placement;\n                                                cx.notify();\n                                            },\n                                        )),\n                                )\n                            });\n\n                            menu\n                        }),\n                ),\n            )\n            .child(\n                section(\"Simple Notification\").child(\n                    Button::new(\"show-notify-0\")\n                        .outline()\n                        .label(\"Show Notification\")\n                        .on_click(cx.listener(|_, _, window, cx| {\n                            window.push_notification(\"This is a notification.\", cx)\n                        })),\n                ),\n            )\n            .child(\n                section(\"Notification with Type\")\n                    .child(\n                        Button::new(\"show-notify-info\")\n                            .info()\n                            .label(\"Info\")\n                            .on_click(cx.listener(|_, _, window, cx| {\n                                window.push_notification(\n                                    (\n                                        NotificationType::Info,\n                                        \"You have been saved file successfully.\",\n                                    ),\n                                    cx,\n                                )\n                            })),\n                    )\n                    .child(\n                        Button::new(\"show-notify-error\")\n                            .danger()\n                            .label(\"Error\")\n                            .on_click(cx.listener(|_, _, window, cx| {\n                                window.push_notification(\n                                    (\n                                        NotificationType::Error,\n                                        \"There have some error occurred. Please try again later.\",\n                                    ),\n                                    cx,\n                                )\n                            })),\n                    )\n                    .child(\n                        Button::new(\"show-notify-success\")\n                            .success()\n                            .label(\"Success\")\n                            .on_click(cx.listener(|_, _, window, cx| {\n                                window.push_notification(\n                                    (\n                                        NotificationType::Success,\n                                        \"We have received your payment successfully.\",\n                                    ),\n                                    cx,\n                                )\n                            })),\n                    )\n                    .child(\n                        Button::new(\"show-notify-warning\")\n                            .warning()\n                            .label(\"Warning\")\n                            .on_click(cx.listener(|_, _, window, cx| {\n                                window.push_notification(\n                                    (\n                                        NotificationType::Warning,\n                                        \"The network is not stable, please check your connection.\",\n                                    ),\n                                    cx,\n                                )\n                            })),\n                    ),\n            )\n            .child(\n                section(\"Unique Notification\").child(\n                    Button::new(\"show-notify-unique\")\n                        .outline()\n                        .label(\"Unique Notification\")\n                        .on_click(cx.listener(|_, _, window, cx| {\n                            window.push_notification(\n                                Notification::info(\"This is a unique notification.\")\n                                    .id::<NotificationStory>()\n                                    .message(\"This is a unique notification.\"),\n                                cx,\n                            )\n                        })),\n                ),\n            )\n            .child(\n                section(\"Unique with Key\").child(\n                    h_flex()\n                        .gap_3()\n                        .child(\n                            Button::new(\"show-notify-unique-key0\")\n                                .outline()\n                                .label(\"A Notification\")\n                                .on_click(cx.listener(|_, _, window, cx| {\n                                    window.push_notification(\n                                        Notification::info(\"This is A unique notification.\")\n                                            .id1::<NotificationStory>(1),\n                                        cx,\n                                    )\n                                })),\n                        )\n                        .child(\n                            Button::new(\"show-notify-unique-key1\")\n                                .outline()\n                                .label(\"B Notification\")\n                                .on_click(cx.listener(|_, _, window, cx| {\n                                    window.push_notification(\n                                        Notification::info(\"This is B unique notification.\")\n                                            .id1::<NotificationStory>(2),\n                                        cx,\n                                    )\n                                })),\n                        ),\n                ),\n            )\n            .child(\n                section(\"With title and action\").child(\n                    Button::new(\"show-notify-with-title\")\n                        .outline()\n                        .label(\"Notification with Title\")\n                        .on_click(cx.listener(|_, _, window, cx| {\n                            struct TestNotification;\n\n                            window.push_notification(\n                                Notification::new()\n                                    .id::<TestNotification>()\n                                    .title(\"Uh oh! Something went wrong.\")\n                                    .message(\"There was a problem with your request.\")\n                                    .action(|_, _, cx| {\n                                        Button::new(\"try-again\").primary().label(\"Retry\").on_click(\n                                            cx.listener(|this, _, window, cx| {\n                                                println!(\"You have clicked the try again action.\");\n                                                this.dismiss(window, cx);\n                                            }),\n                                        )\n                                    })\n                                    .on_click(cx.listener(|_, _, _, cx| {\n                                        println!(\"Notification clicked\");\n                                        cx.notify();\n                                    })),\n                                cx,\n                            )\n                        })),\n                ),\n            )\n            .child(\n                section(\"Custom Notification\").child(\n                    Button::new(\"show-notify-custom\")\n                        .outline()\n                        .label(\"Show Custom Notification\")\n                        .on_click(cx.listener(|_, _, window, cx| {\n                            window.push_notification(\n                                Notification::new().content(|_, _, _| {\n                                    markdown(NOTIFICATION_MARKDOWN).into_any_element()\n                                }),\n                                cx,\n                            )\n                        })),\n                ),\n            )\n            .child({\n                struct ManualOpenNotification;\n\n                section(\"Manual Close Notification\")\n                    .child(\n                        Button::new(\"manual-open-notify\")\n                            .outline()\n                            .label(\"Show\")\n                            .on_click(cx.listener(|_, _, window, cx| {\n                                window.push_notification(\n                                    Notification::new()\n                                        .id::<ManualOpenNotification>()\n                                        .message(\n                                            \"You can close this notification by \\\n                                            clicking the Close button.\",\n                                        )\n                                        .autohide(false),\n                                    cx,\n                                );\n                            })),\n                    )\n                    .child(\n                        Button::new(\"manual-close-notify\")\n                            .outline()\n                            .label(\"Dismiss All\")\n                            .on_click(cx.listener(|_, _, window, cx| {\n                                window.remove_notification::<ManualOpenNotification>(cx);\n                            })),\n                    )\n            })\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/number_input_story.rs",
    "content": "use gpui::{\n    App, AppContext as _, Context, Entity, Focusable, InteractiveElement, IntoElement,\n    ParentElement as _, Render, Styled, Subscription, Window, px,\n};\nuse regex::Regex;\n\nuse crate::section;\nuse gpui_component::{\n    ActiveTheme, Disableable, IconName, Sizable,\n    button::{Button, ButtonVariants},\n    input::{InputEvent, InputState, MaskPattern, NumberInput, NumberInputEvent, StepAction},\n    v_flex,\n};\n\npub fn init(_: &mut App) {}\n\npub struct NumberInputStory {\n    number_input1_value: i64,\n    number_input1: Entity<InputState>,\n    number_input2: Entity<InputState>,\n    number_input2_value: u64,\n    number_input3: Entity<InputState>,\n    number_input3_value: f64,\n    number_input4: Entity<InputState>,\n    number_input4_value: f64,\n    disabled_input: Entity<InputState>,\n\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl super::Story for NumberInputStory {\n    fn title() -> &'static str {\n        \"NumberInput\"\n    }\n\n    fn description() -> &'static str {\n        \"NumberInput design to support + - to adjust the input value.\"\n    }\n\n    fn closable() -> bool {\n        false\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl NumberInputStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let number_input1_value = 1;\n        let number_input1 = cx.new(|cx| {\n            InputState::new(window, cx)\n                .placeholder(\"Normal Integer\")\n                .default_value(number_input1_value.to_string())\n        });\n\n        let number_input2 = cx.new(|cx| {\n            InputState::new(window, cx)\n                .placeholder(\"Unsized Integer\")\n                .pattern(Regex::new(r\"^\\d+$\").unwrap())\n        });\n\n        let number_input3 = cx.new(|cx| {\n            InputState::new(window, cx)\n                .placeholder(\"Mask pattern\")\n                .mask_pattern(MaskPattern::Number {\n                    separator: Some(','),\n                    fraction: Some(2),\n                })\n        });\n\n        let number_input4 = cx.new(|cx| {\n            InputState::new(window, cx)\n                .placeholder(\"Styling\")\n                .mask_pattern(MaskPattern::Number {\n                    separator: Some(','),\n                    fraction: Some(2),\n                })\n        });\n\n        let disabled_input = cx.new(|cx| {\n            InputState::new(window, cx)\n                .default_value(\"100\")\n                .placeholder(\"Disabled\")\n        });\n\n        let _subscriptions = vec![\n            cx.subscribe_in(&number_input1, window, Self::on_input_event),\n            cx.subscribe_in(&number_input1, window, Self::on_number_input_event),\n            cx.subscribe_in(&number_input2, window, Self::on_input_event),\n            cx.subscribe_in(&number_input2, window, Self::on_number_input_event),\n            cx.subscribe_in(&number_input3, window, Self::on_input_event),\n            cx.subscribe_in(&number_input3, window, Self::on_number_input_event),\n            cx.subscribe_in(&number_input4, window, Self::on_input_event),\n            cx.subscribe_in(&number_input4, window, Self::on_number_input_event),\n            cx.subscribe_in(&disabled_input, window, Self::on_input_event),\n            cx.subscribe_in(&disabled_input, window, Self::on_number_input_event),\n        ];\n\n        Self {\n            number_input1,\n            number_input1_value,\n            number_input2,\n            number_input2_value: 0,\n            number_input3,\n            number_input3_value: 0.0,\n            number_input4,\n            number_input4_value: 0.0,\n            disabled_input,\n            _subscriptions,\n        }\n    }\n\n    fn on_input_event(\n        &mut self,\n        state: &Entity<InputState>,\n        event: &InputEvent,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        match event {\n            InputEvent::Change => {\n                let text = state.read(cx).value();\n                if state == &self.number_input1 {\n                    if let Ok(value) = text.parse::<i64>() {\n                        self.number_input1_value = value;\n                    }\n                } else if state == &self.number_input2 {\n                    if let Ok(value) = text.parse::<u64>() {\n                        self.number_input2_value = value;\n                    }\n                } else if state == &self.number_input3 {\n                    if let Ok(value) = text.parse::<f64>() {\n                        self.number_input3_value = value;\n                    }\n                }\n                println!(\"Change: {}\", text);\n            }\n            InputEvent::PressEnter { secondary } => {\n                println!(\"PressEnter secondary: {}\", secondary)\n            }\n            InputEvent::Focus => println!(\"Focus\"),\n            InputEvent::Blur => println!(\"Blur\"),\n        }\n    }\n\n    fn on_number_input_event(\n        &mut self,\n        this: &Entity<InputState>,\n        event: &NumberInputEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        match event {\n            NumberInputEvent::Step(step_action) => match step_action {\n                StepAction::Decrement => {\n                    if this == &self.number_input1 {\n                        self.number_input1_value = self.number_input1_value - 1;\n                        this.update(cx, |input, cx| {\n                            input.set_value(self.number_input1_value.to_string(), window, cx);\n                        });\n                    } else if this == &self.number_input2 {\n                        self.number_input2_value = self.number_input2_value.saturating_sub(1);\n                        this.update(cx, |input, cx| {\n                            input.set_value(self.number_input2_value.to_string(), window, cx);\n                        });\n                    } else if this == &self.number_input3 {\n                        self.number_input3_value = self.number_input3_value - 1.0;\n                        this.update(cx, |input, cx| {\n                            input.set_value(self.number_input3_value.to_string(), window, cx);\n                        });\n                    } else if this == &self.number_input4 {\n                        self.number_input4_value = self.number_input4_value - 1.0;\n                        this.update(cx, |input, cx| {\n                            input.set_value(self.number_input4_value.to_string(), window, cx);\n                        });\n                    }\n                }\n                StepAction::Increment => {\n                    if this == &self.number_input1 {\n                        self.number_input1_value = self.number_input1_value + 1;\n                        this.update(cx, |input, cx| {\n                            input.set_value(self.number_input1_value.to_string(), window, cx);\n                        });\n                    } else if this == &self.number_input2 {\n                        self.number_input2_value = self.number_input2_value + 1;\n                        this.update(cx, |input, cx| {\n                            input.set_value(self.number_input2_value.to_string(), window, cx);\n                        });\n                    } else if this == &self.number_input3 {\n                        self.number_input3_value = self.number_input3_value + 1.0;\n                        this.update(cx, |input, cx| {\n                            input.set_value(self.number_input3_value.to_string(), window, cx);\n                        });\n                    } else if this == &self.number_input4 {\n                        self.number_input4_value = self.number_input4_value + 1.0;\n                        this.update(cx, |input, cx| {\n                            input.set_value(self.number_input4_value.to_string(), window, cx);\n                        });\n                    }\n                }\n            },\n        }\n    }\n}\n\nimpl Focusable for NumberInputStory {\n    fn focus_handle(&self, cx: &gpui::App) -> gpui::FocusHandle {\n        self.number_input1.focus_handle(cx)\n    }\n}\n\nimpl Render for NumberInputStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .id(\"input-story\")\n            .size_full()\n            .justify_start()\n            .gap_3()\n            .child(\n                section(\"Normal Size\")\n                    .max_w(px(200.))\n                    .child(NumberInput::new(&self.number_input1)),\n            )\n            .child(\n                section(\"Disabled\")\n                    .max_w(px(200.))\n                    .child(NumberInput::new(&self.disabled_input).disabled(true)),\n            )\n            .child(\n                section(\"Small Size with suffix\").max_w(px(200.)).child(\n                    NumberInput::new(&self.number_input2)\n                        .small()\n                        .suffix(Button::new(\"info\").ghost().icon(IconName::Info).xsmall()),\n                ),\n            )\n            .child(\n                section(\"With mask pattern\")\n                    .max_w(px(200.))\n                    .child(NumberInput::new(&self.number_input3)),\n            )\n            .child(\n                section(\"Without appearance\").max_w(px(200.)).child(\n                    NumberInput::new(&self.number_input4)\n                        .appearance(false)\n                        .bg(cx.theme().secondary),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/otp_input_story.rs",
    "content": "use gpui::{\n    prelude::FluentBuilder as _, px, App, AppContext as _, Context, Entity, Focusable,\n    InteractiveElement, IntoElement, ParentElement as _, Render, SharedString, Styled,\n    Subscription, Window,\n};\nuse gpui_component::{\n    checkbox::Checkbox,\n    h_flex,\n    input::{InputEvent, OtpInput, OtpState},\n    v_flex, Disableable as _, Sizable, StyledExt,\n};\n\nuse crate::section;\n\npub fn init(_: &mut App) {}\n\npub struct OtpInputStory {\n    otp_masked: bool,\n    otp_state: Entity<OtpState>,\n    otp_value: Option<SharedString>,\n    otp_state_small: Entity<OtpState>,\n    otp_state_large: Entity<OtpState>,\n    otp_state_sized: Entity<OtpState>,\n    otp_state_disabled: Entity<OtpState>,\n\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl super::Story for OtpInputStory {\n    fn title() -> &'static str {\n        \"OtpInput\"\n    }\n\n    fn description() -> &'static str {\n        \"OTP Input uses to one-time password (OTP) input field or number password input field.\"\n    }\n\n    fn closable() -> bool {\n        false\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl OtpInputStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let otp_state = cx.new(|cx| OtpState::new(6, window, cx).masked(true));\n\n        let _subscriptions = vec![\n            cx.subscribe(&otp_state, |this, state, ev: &InputEvent, cx| match ev {\n                InputEvent::Change => {\n                    let text = state.read(cx).value();\n                    this.otp_value = Some(text.clone());\n                    cx.notify();\n                }\n                _ => {}\n            }),\n        ];\n\n        Self {\n            otp_masked: true,\n            otp_state,\n            otp_value: None,\n            otp_state_small: cx.new(|cx| {\n                OtpState::new(6, window, cx)\n                    .default_value(\"123456\")\n                    .masked(true)\n            }),\n            otp_state_large: cx.new(|cx| {\n                OtpState::new(6, window, cx)\n                    .default_value(\"012345\")\n                    .masked(true)\n            }),\n            otp_state_sized: cx.new(|cx| {\n                OtpState::new(4, window, cx)\n                    .masked(true)\n                    .default_value(\"654321\")\n            }),\n            otp_state_disabled: cx.new(|cx| {\n                OtpState::new(6, window, cx)\n                    .masked(true)\n                    .default_value(\"123456\")\n            }),\n            _subscriptions,\n        }\n    }\n\n    fn toggle_opt_masked(&mut self, _: &bool, window: &mut Window, cx: &mut Context<Self>) {\n        self.otp_masked = !self.otp_masked;\n        self.otp_state.update(cx, |state, cx| {\n            state.set_masked(self.otp_masked, window, cx)\n        });\n        self.otp_state_small.update(cx, |state, cx| {\n            state.set_masked(self.otp_masked, window, cx)\n        });\n        self.otp_state_large.update(cx, |state, cx| {\n            state.set_masked(self.otp_masked, window, cx)\n        });\n        self.otp_state_sized.update(cx, |state, cx| {\n            state.set_masked(self.otp_masked, window, cx)\n        });\n        self.otp_state_disabled.update(cx, |state, cx| {\n            state.set_masked(self.otp_masked, window, cx)\n        });\n    }\n}\n\nimpl Focusable for OtpInputStory {\n    fn focus_handle(&self, cx: &gpui::App) -> gpui::FocusHandle {\n        self.otp_state.focus_handle(cx)\n    }\n}\n\nimpl Render for OtpInputStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .id(\"otp-input-story\")\n            .size_full()\n            .gap_5()\n            .child(\n                h_flex().items_center().child(\n                    Checkbox::new(\"otp-mask\")\n                        .label(\"Masked\")\n                        .checked(self.otp_masked)\n                        .on_click(cx.listener(Self::toggle_opt_masked)),\n                ),\n            )\n            .child(\n                section(\"Normal\")\n                    .v_flex()\n                    .child(OtpInput::new(&self.otp_state))\n                    .when_some(self.otp_value.clone(), |this, otp| {\n                        this.child(format!(\"Your OTP: {}\", otp))\n                    }),\n            )\n            .child(section(\"Small\").child(OtpInput::new(&self.otp_state_small).groups(1).small()))\n            .child(section(\"Large\").child(OtpInput::new(&self.otp_state_large).groups(3).large()))\n            .child(\n                section(\"With Size\").child(\n                    OtpInput::new(&self.otp_state_sized)\n                        .groups(1)\n                        .with_size(px(55.)),\n                ),\n            )\n            .child(\n                section(\"Disabled\").child(OtpInput::new(&self.otp_state_disabled).disabled(true)),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/pagination_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render,\n    Styled, Window,\n};\nuse gpui_component::{\n    Disableable, Selectable as _, Sizable, Size,\n    button::{Button, ButtonGroup},\n    pagination::Pagination,\n    v_flex,\n};\n\nuse crate::section;\n\npub struct PaginationStory {\n    basic_page: usize,\n    many_pages_page: usize,\n    compact_page: usize,\n    focus_handle: FocusHandle,\n    size: Size,\n}\n\nimpl super::Story for PaginationStory {\n    fn title() -> &'static str {\n        \"Pagination\"\n    }\n\n    fn description() -> &'static str {\n        \"Pagination with page navigation, next and previous links.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl PaginationStory {\n    pub fn view(_window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self {\n            basic_page: 5,\n            many_pages_page: 1,\n            compact_page: 3,\n            focus_handle: cx.focus_handle(),\n            size: Size::default(),\n        })\n    }\n\n    fn set_size(&mut self, size: Size, _: &mut Window, cx: &mut Context<Self>) {\n        self.size = size;\n        cx.notify();\n    }\n}\n\nimpl Focusable for PaginationStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for PaginationStory {\n    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let entity = cx.entity();\n\n        v_flex()\n            .gap_6()\n            .child(\n                ButtonGroup::new(\"toggle-size\")\n                    .outline()\n                    .compact()\n                    .child(\n                        Button::new(\"xsmall\")\n                            .label(\"XSmall\")\n                            .selected(self.size == Size::XSmall),\n                    )\n                    .child(\n                        Button::new(\"small\")\n                            .label(\"Small\")\n                            .selected(self.size == Size::Small),\n                    )\n                    .child(\n                        Button::new(\"medium\")\n                            .label(\"Medium\")\n                            .selected(self.size == Size::Medium),\n                    )\n                    .child(\n                        Button::new(\"large\")\n                            .label(\"Large\")\n                            .selected(self.size == Size::Large),\n                    )\n                    .on_click(cx.listener(|this, selecteds: &Vec<usize>, window, cx| {\n                        let size = match selecteds[0] {\n                            0 => Size::XSmall,\n                            1 => Size::Small,\n                            2 => Size::Medium,\n                            3 => Size::Large,\n                            _ => Size::Medium,\n                        };\n                        this.set_size(size, window, cx);\n                    })),\n            )\n            .child(\n                section(\"Basic\").child(\n                    Pagination::new(\"basic-pagination\")\n                        .current_page(self.basic_page)\n                        .total_pages(10)\n                        .with_size(self.size)\n                        .on_click({\n                            let entity = entity.clone();\n                            move |page, _, cx| {\n                                entity.update(cx, |this, cx| {\n                                    this.basic_page = *page;\n                                    cx.notify();\n                                });\n                            }\n                        }),\n                ),\n            )\n            .child(\n                section(\"Pagination with 10 visible pages\").child(\n                    Pagination::new(\"many-pages-pagination\")\n                        .current_page(self.many_pages_page)\n                        .total_pages(50)\n                        .visible_pages(10)\n                        .with_size(self.size)\n                        .on_click({\n                            let entity = entity.clone();\n                            move |page, _, cx| {\n                                entity.update(cx, |this, cx| {\n                                    this.many_pages_page = *page;\n                                    cx.notify();\n                                });\n                            }\n                        }),\n                ),\n            )\n            .child(\n                section(\"Compact Style\").child(\n                    Pagination::new(\"compact-pagination\")\n                        .compact()\n                        .current_page(self.compact_page)\n                        .total_pages(10)\n                        .with_size(self.size)\n                        .on_click({\n                            let entity = entity.clone();\n                            move |page, _, cx| {\n                                entity.update(cx, |this, cx| {\n                                    this.compact_page = *page;\n                                    cx.notify();\n                                });\n                            }\n                        }),\n                ),\n            )\n            .child(\n                section(\"Disabled\").child(\n                    Pagination::new(\"disabled-pagination\")\n                        .current_page(4)\n                        .total_pages(10)\n                        .with_size(self.size)\n                        .disabled(true)\n                        .on_click(|_, _, _| {}),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/popover_story.rs",
    "content": "use gpui::{\n    Action, App, AppContext, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,\n    Half, InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement as _, Render,\n    Styled as _, Window, actions, div, px,\n};\nuse gpui_component::{\n    ActiveTheme, Anchor, StyledExt, WindowExt,\n    button::{Button, ButtonVariants as _},\n    divider::Divider,\n    h_flex,\n    input::{Input, InputState},\n    list::{List, ListDelegate, ListItem, ListState},\n    popover::Popover,\n    v_flex,\n};\nuse serde::Deserialize;\n\nuse crate::section;\n\n#[derive(Action, Clone, PartialEq, Eq, Deserialize)]\n#[action(namespace = popover_story, no_json)]\nstruct Info(usize);\n\nactions!(popover_story, [Copy, Paste, Cut, SearchAll, ToggleCheck]);\nconst CONTEXT: &str = \"popover-story\";\npub fn init(cx: &mut App) {\n    cx.bind_keys([\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-c\", Copy, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-c\", Copy, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-v\", Paste, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-v\", Paste, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-x\", Cut, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-x\", Cut, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-shift-f\", SearchAll, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-shift-f\", SearchAll, Some(CONTEXT)),\n    ])\n}\n\nstruct Form {\n    parent: Entity<PopoverStory>,\n    input1: Entity<InputState>,\n}\n\nimpl Form {\n    fn new(parent: Entity<PopoverStory>, window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self {\n            parent,\n            input1: cx.new(|cx| InputState::new(window, cx)),\n        })\n    }\n}\n\nimpl Focusable for Form {\n    fn focus_handle(&self, cx: &App) -> FocusHandle {\n        self.input1.focus_handle(cx)\n    }\n}\n\nstruct DropdownListDelegate {\n    parent: Entity<PopoverStory>,\n}\nimpl ListDelegate for DropdownListDelegate {\n    type Item = ListItem;\n\n    fn items_count(&self, _: usize, _: &App) -> usize {\n        10\n    }\n\n    fn render_item(\n        &mut self,\n        ix: gpui_component::IndexPath,\n        _: &mut Window,\n        _: &mut Context<ListState<Self>>,\n    ) -> Option<Self::Item> {\n        Some(ListItem::new(ix).child(format!(\"Item {}\", ix.row)))\n    }\n\n    fn set_selected_index(\n        &mut self,\n        _: Option<gpui_component::IndexPath>,\n        _: &mut Window,\n        _: &mut Context<gpui_component::list::ListState<Self>>,\n    ) {\n    }\n\n    fn confirm(&mut self, _: bool, _: &mut Window, cx: &mut Context<ListState<Self>>) {\n        self.parent.update(cx, |this, cx| {\n            this.list_popover_open = false;\n            cx.notify();\n        })\n    }\n\n    fn cancel(&mut self, _: &mut Window, cx: &mut Context<ListState<Self>>) {\n        self.parent.update(cx, |this, cx| {\n            this.list_popover_open = false;\n            cx.notify();\n        })\n    }\n}\n\nimpl EventEmitter<DismissEvent> for Form {}\n\nimpl Render for Form {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let parent = self.parent.clone();\n        v_flex()\n            .gap_2()\n            .p_3()\n            .size_full()\n            .child(\"This is a form container.\")\n            .child(\"Click submit to dismiss the popover.\")\n            .child(Input::new(&self.input1))\n            .child(\n                Button::new(\"submit\")\n                    .label(\"Submit\")\n                    .primary()\n                    .on_click(cx.listener(move |_, _, _, cx| {\n                        parent.update(cx, |this, cx| {\n                            this.form_popover_open = false;\n                            cx.notify();\n                        })\n                    })),\n            )\n    }\n}\n\npub struct PopoverStory {\n    focus_handle: FocusHandle,\n    form: Entity<Form>,\n    list: Entity<ListState<DropdownListDelegate>>,\n    form_popover_open: bool,\n    list_popover_open: bool,\n    checked: bool,\n    message: String,\n}\n\nimpl super::Story for PopoverStory {\n    fn title() -> &'static str {\n        \"Popover\"\n    }\n\n    fn description() -> &'static str {\n        \"A popup displays content on top of the main page.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl PopoverStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let form = Form::new(cx.entity(), window, cx);\n        let parent = cx.entity();\n        let list = cx\n            .new(|cx| ListState::new(DropdownListDelegate { parent }, window, cx).searchable(true));\n\n        cx.focus_self(window);\n\n        Self {\n            form,\n            list,\n            checked: true,\n            form_popover_open: false,\n            list_popover_open: false,\n            focus_handle: cx.focus_handle(),\n            message: \"\".to_string(),\n        }\n    }\n\n    fn on_copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {\n        self.message = \"You have clicked copy\".to_string();\n        cx.notify()\n    }\n\n    fn on_cut(&mut self, _: &Cut, _: &mut Window, cx: &mut Context<Self>) {\n        self.message = \"You have clicked cut\".to_string();\n        cx.notify()\n    }\n\n    fn on_paste(&mut self, _: &Paste, _: &mut Window, cx: &mut Context<Self>) {\n        self.message = \"You have clicked paste\".to_string();\n        cx.notify()\n    }\n\n    fn on_search_all(&mut self, _: &SearchAll, _: &mut Window, cx: &mut Context<Self>) {\n        self.message = \"You have clicked search all\".to_string();\n        cx.notify()\n    }\n\n    fn on_action_info(&mut self, info: &Info, _: &mut Window, cx: &mut Context<Self>) {\n        self.message = format!(\"You have clicked info: {}\", info.0);\n        cx.notify()\n    }\n\n    fn on_action_toggle_check(&mut self, _: &ToggleCheck, _: &mut Window, cx: &mut Context<Self>) {\n        self.checked = !self.checked;\n        self.message = format!(\"You have clicked toggle check: {}\", self.checked);\n        cx.notify()\n    }\n}\n\nimpl Focusable for PopoverStory {\n    fn focus_handle(&self, _cx: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for PopoverStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let form = self.form.clone();\n\n        v_flex()\n            .key_context(CONTEXT)\n            .track_focus(&self.focus_handle)\n            .on_action(cx.listener(Self::on_copy))\n            .on_action(cx.listener(Self::on_cut))\n            .on_action(cx.listener(Self::on_paste))\n            .on_action(cx.listener(Self::on_search_all))\n            .on_action(cx.listener(Self::on_action_info))\n            .on_action(cx.listener(Self::on_action_toggle_check))\n            .size_full()\n            .gap_6()\n            .child(\n                section(\"Basic Popover\").child(\n                    Popover::new(\"popover-0\")\n                        .max_w(px(600.))\n                        .trigger(Button::new(\"btn\").outline().label(\"Popover\"))\n                        .gap_2()\n                        .text_sm()\n                        .w(px(400.))\n                        .child(\"Hello, this is a Popover.\")\n                        .child(Divider::horizontal())\n                        .child(\n                            \"You can put any content here, including text,\\\n                            buttons, forms, and more.\",\n                        ),\n                ),\n            )\n            .child(\n                section(\"Popover with Form\").child(\n                    Popover::new(\"popover-form\")\n                        .p_0()\n                        .text_sm()\n                        .trigger(Button::new(\"pop\").outline().label(\"Popup Form\"))\n                        .track_focus(&form.focus_handle(cx))\n                        .open(self.form_popover_open)\n                        .on_open_change(cx.listener(move |this, open, _, cx| {\n                            println!(\"Popover form open changed: {}\", open);\n                            this.form_popover_open = *open;\n                            cx.notify();\n                        }))\n                        .child(form.clone()),\n                ),\n            )\n            .child(\n                section(\"Popover with List\").child(\n                    Popover::new(\"popover-list\")\n                        .p_0()\n                        .text_sm()\n                        .open(self.list_popover_open)\n                        .on_open_change(cx.listener(move |this, open, _, cx| {\n                            this.list_popover_open = *open;\n                            cx.notify();\n                        }))\n                        .trigger(Button::new(\"pop\").outline().label(\"Popup List\"))\n                        .track_focus(&self.list.focus_handle(cx))\n                        .child(List::new(&self.list))\n                        .w_64()\n                        .h(px(200.)),\n                ),\n            )\n            .child(\n                section(\"Right click to open Popover\").child(\n                    Popover::new(\"popover-right-click\")\n                        .mouse_button(MouseButton::Right)\n                        .trigger(Button::new(\"btn\").outline().label(\"Right Click Popover\"))\n                        .max_w(px(600.))\n                        .content(|_, _, cx| {\n                            v_flex()\n                                .gap_2()\n                                .child(\"Hello, this is a Popover on the Bottom Right.\")\n                                .child(Divider::horizontal())\n                                .child(\n                                    Button::new(\"info1\")\n                                        .primary()\n                                        .label(\"Dismiss\")\n                                        .w(px(80.))\n                                        .on_click(cx.listener(|_, _, window, cx| {\n                                            window.push_notification(\n                                                \"You have clicked dismiss via DismissEvent.\",\n                                                cx,\n                                            );\n                                            cx.emit(DismissEvent);\n                                        })),\n                                )\n                        }),\n                ),\n            )\n            .child(\n                section(\"Styling Popover\").child(\n                    Popover::new(\"popover-1\")\n                        .trigger(Button::new(\"btn\").outline().label(\"Style Popover\"))\n                        .appearance(false)\n                        .py_1()\n                        .px_2()\n                        .bg(cx.theme().primary)\n                        .text_color(cx.theme().primary_foreground)\n                        .max_w(px(600.))\n                        .rounded(cx.theme().radius.half())\n                        .text_sm()\n                        .shadow_2xl()\n                        .child(\"A styled Popover with custom background and text color.\"),\n                ),\n            )\n            .child(\n                section(\"Default Open\").child(\n                    Popover::new(\"default-open-popover\")\n                        .default_open(true)\n                        .trigger(\n                            Button::new(\"default-open-btn\")\n                                .label(\"Default Open\")\n                                .outline(),\n                        )\n                        .child(\"This popover is open by default when first rendered.\"),\n                ),\n            )\n            .child(\n                section(\"Popover Anchor\")\n                    .min_h(px(360.))\n                    .v_flex()\n                    .child(\n                        div().absolute().top_0().left_0().w_full().h_10().child(\n                            h_flex()\n                                .items_center()\n                                .justify_between()\n                                .child(\n                                    Popover::new(\"anchor-top-left\")\n                                        .max_w(px(600.))\n                                        .anchor(Anchor::TopLeft)\n                                        .trigger(Button::new(\"btn\").outline().label(\"TopLeft\"))\n                                        .child(\"This is a Popover on the Top Left.\"),\n                                )\n                                .child(\n                                    Popover::new(\"anchor-top-center\")\n                                        .max_w(px(600.))\n                                        .anchor(Anchor::TopCenter)\n                                        .trigger(Button::new(\"btn\").outline().label(\"TopCenter\"))\n                                        .child(\"This is a Popover on the Top Center.\"),\n                                )\n                                .child(\n                                    Popover::new(\"anchor-top-right\")\n                                        .anchor(Anchor::TopRight)\n                                        .trigger(Button::new(\"btn\").outline().label(\"TopRight\"))\n                                        .child(\"This is a Popover on the Top Right.\"),\n                                ),\n                        ),\n                    )\n                    .child(\n                        div().absolute().bottom_0().left_0().w_full().h_10().child(\n                            h_flex()\n                                .items_center()\n                                .justify_between()\n                                .child(\n                                    Popover::new(\"anchor-bottom-left\")\n                                        .trigger(Button::new(\"btn\").outline().label(\"BottomLeft\"))\n                                        .anchor(Anchor::BottomLeft)\n                                        .child(\"This is a Popover on the Bottom Left.\"),\n                                )\n                                .child(\n                                    Popover::new(\"anchor-bottom-center\")\n                                        .trigger(Button::new(\"btn\").outline().label(\"BottomCenter\"))\n                                        .anchor(Anchor::BottomCenter)\n                                        .child(\"This is a Popover on the Bottom Center.\"),\n                                )\n                                .child(\n                                    Popover::new(\"anchor-bottom-right\")\n                                        .anchor(Anchor::BottomRight)\n                                        .trigger(Button::new(\"btn\").outline().label(\"BottomRight\"))\n                                        .child(\"This is a Popover on the Bottom Right.\"),\n                                ),\n                        ),\n                    ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/progress_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled, Task,\n    Window, div, px,\n};\nuse gpui_component::{\n    ActiveTheme, IconName, Sizable,\n    button::Button,\n    h_flex,\n    progress::{Progress, ProgressCircle},\n    v_flex,\n};\nuse std::time::Duration;\n\nuse crate::section;\n\npub struct ProgressStory {\n    focus_handle: gpui::FocusHandle,\n    value: f32,\n    _task: Option<Task<()>>,\n}\n\nimpl super::Story for ProgressStory {\n    fn title() -> &'static str {\n        \"Progress\"\n    }\n\n    fn description() -> &'static str {\n        \"Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl ProgressStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            value: 25.,\n            _task: None,\n        }\n    }\n\n    pub fn set_value(&mut self, value: f32) {\n        self.value = value;\n    }\n\n    fn start_animation(&mut self, cx: &mut Context<Self>) {\n        self.value = 0.;\n\n        self._task = Some(cx.spawn({\n            let entity = cx.entity();\n            async move |_, cx| {\n                loop {\n                    cx.background_executor()\n                        .timer(Duration::from_millis(15))\n                        .await;\n\n                    let mut need_break = false;\n                    _ = entity.update(cx, |this, cx| {\n                        this.value = (this.value + 2.).min(100.);\n                        cx.notify();\n\n                        if this.value >= 100. {\n                            this._task = None;\n                            need_break = true;\n                        }\n                    });\n\n                    if need_break {\n                        break;\n                    }\n                }\n            }\n        }));\n    }\n}\n\nimpl Focusable for ProgressStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for ProgressStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .w_full()\n            .gap_3()\n            .child(\n                h_flex()\n                    .w_full()\n                    .gap_3()\n                    .justify_between()\n                    .child(\n                        h_flex()\n                            .gap_2()\n                            .child(Button::new(\"button-1\").small().label(\"0%\").on_click(\n                                cx.listener(|this, _, _, _| {\n                                    this.set_value(0.);\n                                }),\n                            ))\n                            .child(Button::new(\"button-2\").small().label(\"25%\").on_click(\n                                cx.listener(|this, _, _, _| {\n                                    this.set_value(25.);\n                                }),\n                            ))\n                            .child(Button::new(\"button-3\").small().label(\"75%\").on_click(\n                                cx.listener(|this, _, _, _| {\n                                    this.set_value(75.);\n                                }),\n                            ))\n                            .child(Button::new(\"button-4\").small().label(\"100%\").on_click(\n                                cx.listener(|this, _, _, _| {\n                                    this.set_value(100.);\n                                }),\n                            ))\n                            .child(\n                                Button::new(\"circle-animation-button\")\n                                    .small()\n                                    .icon(IconName::Play)\n                                    .on_click(cx.listener(|this, _, _, cx| {\n                                        this.start_animation(cx);\n                                    })),\n                            ),\n                    )\n                    .child(\n                        h_flex()\n                            .gap_2()\n                            .child(\n                                Button::new(\"circle-button-5\")\n                                    .icon(IconName::Minus)\n                                    .on_click(cx.listener(|this, _, _, _| {\n                                        this.set_value((this.value - 1.).max(0.));\n                                    })),\n                            )\n                            .child(\n                                Button::new(\"circle-button-6\")\n                                    .icon(IconName::Plus)\n                                    .on_click(cx.listener(|this, _, _, _| {\n                                        this.set_value((this.value + 1.).min(100.));\n                                    })),\n                            ),\n                    ),\n            )\n            .child(\n                section(\"Progress Bar\")\n                    .max_w_md()\n                    .child(Progress::new(\"progress-1\").value(self.value)),\n            )\n            .child(\n                section(\"Custom Style\").max_w_md().child(\n                    Progress::new(\"progress-2\")\n                        .value(32.)\n                        .h(px(16.))\n                        .rounded(px(2.))\n                        .color(cx.theme().green_light)\n                        .border_2()\n                        .border_color(cx.theme().green),\n                ),\n            )\n            .child(\n                section(\"Circle Progress\").max_w_md().child(\n                    ProgressCircle::new(\"circle-progress-1\")\n                        .value(self.value)\n                        .size_20()\n                        .child(\n                            v_flex()\n                                .size_full()\n                                .items_center()\n                                .justify_center()\n                                .gap_1()\n                                .child(\n                                    div()\n                                        .child(format!(\"{}%\", self.value))\n                                        .text_color(cx.theme().progress_bar),\n                                )\n                                .child(div().child(\"Loading\").text_xs()),\n                        ),\n                ),\n            )\n            .child(\n                section(\"With size\").max_w_md().child(\n                    h_flex()\n                        .gap_2()\n                        .child(\n                            ProgressCircle::new(\"circle-progress-1\")\n                                .value(self.value)\n                                .large(),\n                        )\n                        .child(ProgressCircle::new(\"circle-progress-1\").value(self.value))\n                        .child(\n                            ProgressCircle::new(\"circle-progress-1\")\n                                .value(self.value)\n                                .small(),\n                        )\n                        .child(\n                            ProgressCircle::new(\"circle-progress-1\")\n                                .value(self.value)\n                                .xsmall(),\n                        ),\n                ),\n            )\n            .child(\n                section(\"With Label\").max_w_md().child(\n                    h_flex()\n                        .gap_2()\n                        .child(\n                            ProgressCircle::new(\"circle-progress-1\")\n                                .color(cx.theme().primary)\n                                .value(self.value)\n                                .size_4(),\n                        )\n                        .child(\"Downloading...\"),\n                ),\n            )\n            .child(\n                section(\"Circle with Color\").max_w_md().child(\n                    ProgressCircle::new(\"circle-progress-1\")\n                        .color(cx.theme().yellow)\n                        .value(self.value)\n                        .size_12(),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/radio_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled,\n    Window, div, px,\n};\n\nuse gpui_component::{\n    ActiveTheme, Sizable, h_flex,\n    radio::{Radio, RadioGroup},\n    v_flex,\n};\n\nuse crate::section;\n\npub struct RadioStory {\n    focus_handle: gpui::FocusHandle,\n    radio_check1: bool,\n    radio_check2: bool,\n    radio_group_checked: Option<usize>,\n}\n\nimpl super::Story for RadioStory {\n    fn title() -> &'static str {\n        \"Radio\"\n    }\n\n    fn description() -> &'static str {\n        \"A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl RadioStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            radio_check1: false,\n            radio_check2: true,\n            radio_group_checked: Some(1),\n        }\n    }\n}\n\nimpl Focusable for RadioStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for RadioStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_6()\n            .child(\n                section(\"Radio\")\n                    .max_w_md()\n                    .child(\n                        Radio::new(\"radio1\")\n                            .checked(self.radio_check1)\n                            .on_click(cx.listener(|this, checked, _, _| {\n                                this.radio_check1 = *checked;\n                            })),\n                    )\n                    .child(\n                        Radio::new(\"radio2\")\n                            .label(\"Radio 2\")\n                            .checked(self.radio_check2)\n                            .on_click(cx.listener(|this, checked, _, _| {\n                                this.radio_check2 = *checked;\n                            })),\n                    ),\n            )\n            .child(\n                section(\"Disabled\")\n                    .child(Radio::new(\"a\").label(\"Disabled\").disabled(true))\n                    .child(\n                        Radio::new(\"b\")\n                            .label(\"Disabled with Checked\")\n                            .checked(true)\n                            .disabled(true),\n                    ),\n            )\n            .child(\n                section(\"Multi-line Label\").child(\n                    Radio::new(\"radio3\")\n                        .label(\"The long long label text.\")\n                        .child(\n                            div()\n                                .text_color(cx.theme().muted_foreground)\n                                .child(\"This line should wrap when the text is too long.\"),\n                        )\n                        .w(px(300.))\n                        .checked(true)\n                        .disabled(true),\n                ),\n            )\n            .child(\n                section(\"Sizeable\").child(\n                    h_flex()\n                        .h_full()\n                        .gap_x_4()\n                        .child(\n                            Radio::new(\"xsmall\")\n                                .label(\"Small\")\n                                .xsmall()\n                                .checked(self.radio_check2)\n                                .on_click(cx.listener(|this, v, _, _| {\n                                    this.radio_check2 = *v;\n                                })),\n                        )\n                        .child(\n                            Radio::new(\"large\")\n                                .label(\"Large\")\n                                .large()\n                                .checked(self.radio_check2)\n                                .on_click(cx.listener(|this, v, _, _| {\n                                    this.radio_check2 = *v;\n                                })),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Radio Group\").max_w_md().child(\n                    v_flex().child(\n                        RadioGroup::horizontal(\"radio_group_1\")\n                            .children([\"One\", \"Two\", \"Three\"])\n                            .selected_index(self.radio_group_checked)\n                            .on_click(cx.listener(|this, selected_ix: &usize, _, cx| {\n                                this.radio_group_checked = Some(*selected_ix);\n                                cx.notify();\n                            })),\n                    ),\n                ),\n            )\n            .child(\n                section(\"Radio Group Vertical (With container style)\")\n                    .max_w_md()\n                    .child(\n                        v_flex().items_center().content_center().child(\n                            RadioGroup::vertical(\"radio_group_2\")\n                                .w(px(220.))\n                                .p_2()\n                                .border_1()\n                                .border_color(cx.theme().border)\n                                .rounded(cx.theme().radius)\n                                .disabled(true)\n                                .child(Radio::new(\"one1\").label(\"United States\"))\n                                .child(Radio::new(\"one2\").label(\"Canada\"))\n                                .child(Radio::new(\"one3\").label(\"Mexico\"))\n                                .selected_index(Some(1)),\n                        ),\n                    ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/rating_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled, Window,\n};\nuse gpui_component::{\n    ActiveTheme, IconName, Selectable as _, Sizable as _, Size,\n    button::{Button, ButtonGroup},\n    h_flex,\n    rating::Rating,\n    v_flex,\n};\n\nuse crate::section;\n\npub struct RatingStory {\n    focus_handle: gpui::FocusHandle,\n    size: Size,\n    value: usize,\n}\n\nimpl super::Story for RatingStory {\n    fn title() -> &'static str {\n        \"Rating\"\n    }\n\n    fn description() -> &'static str {\n        \"A simple interactive star rating component.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl RatingStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            size: Size::default(),\n            value: 3,\n        }\n    }\n}\n\nimpl Focusable for RatingStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\npub fn init(_cx: &mut App) {\n    // No global init required for RatingStory\n}\n\nimpl Render for RatingStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .w_full()\n            .gap_3()\n            .child(\n                h_flex().w_full().gap_3().child(\n                    ButtonGroup::new(\"toggle-size\")\n                        .outline()\n                        .compact()\n                        .child(\n                            Button::new(\"xsmall\")\n                                .label(\"XSmall\")\n                                .selected(self.size == Size::XSmall),\n                        )\n                        .child(\n                            Button::new(\"small\")\n                                .label(\"Small\")\n                                .selected(self.size == Size::Small),\n                        )\n                        .child(\n                            Button::new(\"medium\")\n                                .label(\"Medium\")\n                                .selected(self.size == Size::Medium),\n                        )\n                        .child(\n                            Button::new(\"large\")\n                                .label(\"Large\")\n                                .selected(self.size == Size::Large),\n                        )\n                        .on_click(cx.listener(|this, selecteds: &Vec<usize>, _, cx| {\n                            let size = match selecteds[0] {\n                                0 => Size::XSmall,\n                                1 => Size::Small,\n                                2 => Size::Medium,\n                                3 => Size::Large,\n                                _ => unreachable!(),\n                            };\n                            this.size = size;\n                            cx.notify();\n                        })),\n                ),\n            )\n            .child(\n                section(\"Basic Rating\").max_w_md().child(\n                    v_flex()\n                        .w_full()\n                        .gap_3()\n                        .justify_center()\n                        .items_center()\n                        .child(\n                            Rating::new(\"rating-1\")\n                                .with_size(self.size)\n                                .value(self.value)\n                                .max(5)\n                                .on_click(cx.listener(|this, value: &usize, _, cx| {\n                                    this.value = *value;\n                                    cx.notify();\n                                })),\n                        )\n                        .child(\n                            h_flex()\n                                .gap_x_2()\n                                .child(\n                                    Button::new(\"r-dec\")\n                                        .small()\n                                        .outline()\n                                        .icon(IconName::Minus)\n                                        .on_click(cx.listener(|this, _, _, cx| {\n                                            let v = this.value.saturating_sub(1);\n                                            this.value = v;\n                                            cx.notify();\n                                        })),\n                                )\n                                .child(\n                                    Button::new(\"r-inc\")\n                                        .small()\n                                        .outline()\n                                        .icon(IconName::Plus)\n                                        .on_click(cx.listener(|this, _, _, cx| {\n                                            let v = (this.value + 1).min(5);\n                                            this.value = v;\n                                            cx.notify();\n                                        })),\n                                ),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Disabled\").max_w_md().child(\n                    Rating::new(\"rating-2\")\n                        .with_size(self.size)\n                        .value(2)\n                        .color(cx.theme().green)\n                        .max(5)\n                        .disabled(true),\n                ),\n            )\n            .child(\n                section(\"Custom Color\").max_w_md().child(\n                    Rating::new(\"rating-3\")\n                        .large()\n                        .value(self.value)\n                        .color(cx.theme().green)\n                        .max(5),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/resizable_story.rs",
    "content": "use gpui::{\n    AnyElement, App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement,\n    ParentElement as _, Pixels, Render, SharedString, Styled, Window, div, px,\n};\nuse gpui_component::{\n    ActiveTheme,\n    resizable::{h_resizable, resizable_panel, v_resizable},\n    v_flex,\n};\n\npub struct ResizableStory {\n    focus_handle: FocusHandle,\n}\n\nimpl super::Story for ResizableStory {\n    fn title() -> &'static str {\n        \"Resizable\"\n    }\n\n    fn description() -> &'static str {\n        \"The resizable panels.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl Focusable for ResizableStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl ResizableStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut App) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n}\n\nfn panel_box(content: impl Into<SharedString>, _: &App) -> AnyElement {\n    div()\n        .p_4()\n        .size_full()\n        .child(content.into())\n        .into_any_element()\n}\n\nimpl Render for ResizableStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .size_full()\n            .gap_6()\n            .child(\n                div()\n                    .h(px(600.))\n                    .border_1()\n                    .border_color(cx.theme().border)\n                    .child(\n                        v_resizable(\"resizable-1\")\n                            .on_resize(|state, _, cx| {\n                                println!(\"Resized: {:?}\", state.read(cx).sizes());\n                            })\n                            .child(\n                                h_resizable(\"resizable-1.1\")\n                                    .size(px(150.))\n                                    .child(\n                                        resizable_panel()\n                                            .size(px(150.))\n                                            .size_range(px(120.)..px(300.))\n                                            .child(panel_box(\"Left (120px .. 300px)\", cx)),\n                                    )\n                                    .child(panel_box(\"Center\", cx))\n                                    .child(\n                                        resizable_panel()\n                                            .size(px(300.))\n                                            .child(panel_box(\"Right\", cx)),\n                                    ),\n                            )\n                            .child(panel_box(\"Center\", cx))\n                            .child(\n                                resizable_panel()\n                                    .size(px(80.))\n                                    .size_range(px(80.)..Pixels::MAX)\n                                    .child(panel_box(\"Bottom (80px .. 150px)\", cx)),\n                            ),\n                    ),\n            )\n            .child(\n                div()\n                    .h(px(400.))\n                    .border_1()\n                    .border_color(cx.theme().border)\n                    .child(\n                        h_resizable(\"resizable-3\")\n                            .child(\n                                resizable_panel()\n                                    .size(px(200.))\n                                    .size_range(px(200.)..px(400.))\n                                    .child(panel_box(\"Left 2\", cx)),\n                            )\n                            .child(panel_box(\"Right (Grow)\", cx)),\n                    ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/scrollbar_story.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Pixels,\n    Render, Size, Styled, UniformListScrollHandle, Window, div, px, size, uniform_list,\n};\nuse gpui_component::{\n    ActiveTheme as _, Selectable,\n    button::{Button, ButtonGroup},\n    h_flex,\n    scroll::ScrollableElement,\n    v_flex,\n};\n\npub struct ScrollbarStory {\n    focus_handle: FocusHandle,\n    items: Rc<Vec<String>>,\n    item_sizes: Rc<Vec<Size<Pixels>>>,\n    test_width: Pixels,\n    size_mode: usize,\n    scroll_handle: UniformListScrollHandle,\n}\n\nconst ITEM_HEIGHT: Pixels = px(50.);\n\nimpl ScrollbarStory {\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        let items: Rc<Vec<String>> = Rc::new((0..5000).map(|i| format!(\"Item {}\", i)).collect());\n        let test_width = px(3000.);\n        let item_sizes = items\n            .iter()\n            .map(|_| size(test_width, ITEM_HEIGHT))\n            .collect::<Vec<_>>();\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            items,\n            item_sizes: Rc::new(item_sizes),\n            test_width,\n            size_mode: 0,\n            scroll_handle: UniformListScrollHandle::new(),\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    pub fn change_test_cases(&mut self, n: usize, cx: &mut Context<Self>) {\n        self.size_mode = n;\n        if n == 0 {\n            self.items = Rc::new((0..5000).map(|i| format!(\"Item {}\", i)).collect());\n            self.test_width = px(3000.);\n        } else if n == 1 {\n            self.items = Rc::new((0..100).map(|i| format!(\"Item {}\", i)).collect());\n            self.test_width = px(10000.);\n        } else if n == 2 {\n            self.items = Rc::new((0..500000).map(|i| format!(\"Item {}\", i)).collect());\n            self.test_width = px(10000.);\n        } else {\n            self.items = Rc::new((0..5).map(|i| format!(\"Item {}\", i)).collect());\n            self.test_width = px(10000.);\n        }\n\n        self.item_sizes = self\n            .items\n            .iter()\n            .map(|_| size(self.test_width, ITEM_HEIGHT))\n            .collect::<Vec<_>>()\n            .into();\n        cx.notify();\n    }\n\n    fn render_buttons(&mut self, cx: &mut Context<Self>) -> impl IntoElement {\n        h_flex().gap_2().justify_between().child(\n            h_flex().gap_2().child(\n                ButtonGroup::new(\"test-cases\")\n                    .outline()\n                    .compact()\n                    .child(\n                        Button::new(\"test-0\")\n                            .label(\"Size 0\")\n                            .selected(self.size_mode == 0),\n                    )\n                    .child(\n                        Button::new(\"test-1\")\n                            .label(\"Size 1\")\n                            .selected(self.size_mode == 1),\n                    )\n                    .child(\n                        Button::new(\"test-2\")\n                            .label(\"Size 2\")\n                            .selected(self.size_mode == 2),\n                    )\n                    .child(\n                        Button::new(\"test-3\")\n                            .label(\"Size 3\")\n                            .selected(self.size_mode == 3),\n                    )\n                    .on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {\n                        if clicks.contains(&0) {\n                            view.change_test_cases(0, cx)\n                        } else if clicks.contains(&1) {\n                            view.change_test_cases(1, cx)\n                        } else if clicks.contains(&2) {\n                            view.change_test_cases(2, cx)\n                        } else if clicks.contains(&3) {\n                            view.change_test_cases(3, cx)\n                        }\n                    })),\n            ),\n        )\n    }\n}\n\nimpl super::Story for ScrollbarStory {\n    fn title() -> &'static str {\n        \"Scrollbar\"\n    }\n\n    fn description() -> &'static str {\n        \"Add scrollbar to a scrollable element.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl Focusable for ScrollbarStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for ScrollbarStory {\n    fn render(\n        &mut self,\n        _: &mut gpui::Window,\n        cx: &mut gpui::Context<Self>,\n    ) -> impl gpui::IntoElement {\n        v_flex()\n            .size_full()\n            .gap_4()\n            .child(self.render_buttons(cx))\n            .child({\n                div()\n                    .relative()\n                    .border_1()\n                    .border_color(cx.theme().border)\n                    .flex_1()\n                    .child(\n                        uniform_list(\"list\", self.items.len(), {\n                            let items = self.items.clone();\n                            move |visible_range, _, cx| {\n                                let mut elements = Vec::with_capacity(visible_range.len());\n                                for ix in visible_range {\n                                    let item = &items[ix];\n                                    elements.push(\n                                        div()\n                                            .h(ITEM_HEIGHT)\n                                            .pt_1()\n                                            .items_center()\n                                            .justify_center()\n                                            .text_sm()\n                                            .child(\n                                                div()\n                                                    .p_2()\n                                                    .bg(cx.theme().secondary)\n                                                    .child(item.to_string()),\n                                            ),\n                                    );\n                                }\n                                elements\n                            }\n                        })\n                        .py_1()\n                        .px_3()\n                        .size_full()\n                        .track_scroll(&self.scroll_handle),\n                    )\n                    .vertical_scrollbar(&self.scroll_handle)\n            })\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/select_story.rs",
    "content": "use gpui::*;\nuse gpui_component::{button::*, checkbox::*, divider::*, input::*, select::*, *};\nuse itertools::Itertools as _;\nuse serde::{Deserialize, Serialize};\n\nuse crate::section;\n\npub fn init(_: &mut App) {}\n\n#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\nstruct Country {\n    name: SharedString,\n    code: SharedString,\n}\n\nimpl Country {\n    pub fn letter_prefix(&self) -> char {\n        self.name.chars().next().unwrap_or(' ')\n    }\n}\n\nimpl SelectItem for Country {\n    type Value = SharedString;\n\n    fn title(&self) -> SharedString {\n        self.name.clone()\n    }\n\n    fn display_title(&self) -> Option<gpui::AnyElement> {\n        Some(format!(\"{} ({})\", self.name, self.code).into_any_element())\n    }\n\n    fn value(&self) -> &Self::Value {\n        &self.code\n    }\n}\n\npub struct SelectStory {\n    disabled: bool,\n    country_select: Entity<SelectState<SearchableVec<SelectGroup<Country>>>>,\n    fruit_select: Entity<SelectState<SearchableVec<&'static str>>>,\n    simple_select1: Entity<SelectState<Vec<&'static str>>>,\n    simple_select2: Entity<SelectState<SearchableVec<&'static str>>>,\n    simple_select3: Entity<SelectState<Vec<SharedString>>>,\n    disabled_select: Entity<SelectState<Vec<SharedString>>>,\n    appearance_select: Entity<SelectState<Vec<SharedString>>>,\n    input_state: Entity<InputState>,\n}\n\nimpl super::Story for SelectStory {\n    fn title() -> &'static str {\n        \"Select\"\n    }\n\n    fn description() -> &'static str {\n        \"Displays a list of options for the user to pick from—triggered by a button.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl Focusable for SelectStory {\n    fn focus_handle(&self, cx: &gpui::App) -> gpui::FocusHandle {\n        self.fruit_select.focus_handle(cx)\n    }\n}\n\nimpl SelectStory {\n    fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        let countries =\n            serde_json::from_str::<Vec<Country>>(include_str!(\"../fixtures/countries.json\"))\n                .unwrap();\n        let mut grouped_countries: SearchableVec<SelectGroup<Country>> = SearchableVec::new(vec![]);\n        for (prefix, items) in countries.iter().chunk_by(|c| c.letter_prefix()).into_iter() {\n            let items = items.cloned().collect::<Vec<Country>>();\n            grouped_countries.push(SelectGroup::new(prefix.to_string()).items(items));\n        }\n\n        let country_select = cx.new(|cx| {\n            SelectState::new(\n                grouped_countries,\n                Some(IndexPath::default().row(8).section(2)),\n                window,\n                cx,\n            )\n            .searchable(true)\n        });\n        let appearance_select = cx.new(|cx| {\n            SelectState::new(\n                vec![\n                    \"CN\".into(),\n                    \"US\".into(),\n                    \"HK\".into(),\n                    \"JP\".into(),\n                    \"KR\".into(),\n                ],\n                Some(IndexPath::default()),\n                window,\n                cx,\n            )\n        });\n        let input_state = cx.new(|cx| InputState::new(window, cx).placeholder(\"Your phone number\"));\n\n        let fruits = SearchableVec::new(vec![\n            \"Apple\",\n            \"Orange\",\n            \"Banana\",\n            \"Grape\",\n            \"Pineapple\",\n            \"Watermelon & This is a long long long long long long long long long title\",\n            \"Avocado\",\n        ]);\n        let fruit_select = cx.new(|cx| SelectState::new(fruits, None, window, cx).searchable(true));\n\n        cx.new(|cx| {\n            cx.subscribe_in(&country_select, window, Self::on_select_event)\n                .detach();\n\n            Self {\n                disabled: false,\n                country_select,\n                fruit_select,\n                simple_select1: cx.new(|cx| {\n                    SelectState::new(\n                        vec![\n                            \"GPUI\", \"Iced\", \"egui\", \"Makepad\", \"Slint\", \"QT\", \"ImGui\", \"Cocoa\",\n                            \"WinUI\",\n                        ],\n                        Some(IndexPath::default()),\n                        window,\n                        cx,\n                    )\n                }),\n                simple_select2: cx.new(|cx| {\n                    let mut select = SelectState::new(SearchableVec::new(vec![]), None, window, cx)\n                        .searchable(true);\n\n                    select.set_items(\n                        SearchableVec::new(vec![\"Rust\", \"Go\", \"C++\", \"JavaScript\"]),\n                        window,\n                        cx,\n                    );\n\n                    select\n                }),\n                simple_select3: cx\n                    .new(|cx| SelectState::new(Vec::<SharedString>::new(), None, window, cx)),\n                disabled_select: cx\n                    .new(|cx| SelectState::new(Vec::<SharedString>::new(), None, window, cx)),\n                appearance_select,\n                input_state,\n            }\n        })\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        Self::new(window, cx)\n    }\n\n    fn on_select_event(\n        &mut self,\n        _: &Entity<SelectState<SearchableVec<SelectGroup<Country>>>>,\n        event: &SelectEvent<SearchableVec<SelectGroup<Country>>>,\n        _window: &mut Window,\n        _cx: &mut Context<Self>,\n    ) {\n        match event {\n            SelectEvent::Confirm(value) => println!(\"Selected country: {:?}\", value),\n        }\n    }\n\n    fn toggle_disabled(&mut self, disabled: bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.disabled = disabled;\n        cx.notify();\n    }\n}\n\nimpl Render for SelectStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .size_full()\n            .gap_4()\n            .child(\n                Checkbox::new(\"disable-selects\")\n                    .label(\"Disabled\")\n                    .checked(self.disabled)\n                    .on_click(cx.listener(|this, checked, window, cx| {\n                        this.toggle_disabled(*checked, window, cx);\n                    })),\n            )\n            .child(\n                section(\"Select\").max_w_128().child(\n                    Select::new(&self.country_select)\n                        .search_placeholder(\"Search country by name or code\")\n                        .cleanable(true)\n                        .disabled(self.disabled),\n                ),\n            )\n            .child(\n                section(\"Searchable\").max_w_128().child(\n                    Select::new(&self.fruit_select)\n                        .disabled(self.disabled)\n                        .icon(IconName::Search)\n                        .w(px(320.))\n                        .menu_width(px(400.)),\n                ),\n            )\n            .child(\n                section(\"Disabled\")\n                    .max_w_128()\n                    .child(Select::new(&self.disabled_select).disabled(true)),\n            )\n            .child(\n                section(\"With preview label\").max_w_128().child(\n                    Select::new(&self.simple_select1)\n                        .disabled(self.disabled)\n                        .small()\n                        .placeholder(\"UI\")\n                        .title_prefix(\"UI: \"),\n                ),\n            )\n            .child(\n                section(\"Searchable Select\").max_w_128().child(\n                    Select::new(&self.simple_select2)\n                        .disabled(self.disabled)\n                        .small()\n                        .placeholder(\"Language\")\n                        .title_prefix(\"Language: \"),\n                ),\n            )\n            .child(\n                section(\"Empty Items\").max_w_128().child(\n                    Select::new(&self.simple_select3)\n                        .disabled(self.disabled)\n                        .small()\n                        .empty(\n                            h_flex()\n                                .h_24()\n                                .justify_center()\n                                .text_color(cx.theme().muted_foreground)\n                                .child(\"No Data\"),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Appearance false with Input\").max_w_128().child(\n                    h_flex()\n                        .border_1()\n                        .border_color(cx.theme().input)\n                        .rounded(cx.theme().radius_lg)\n                        .text_color(cx.theme().secondary_foreground)\n                        .w_full()\n                        .gap_1()\n                        .child(\n                            div().w(px(140.)).child(\n                                Select::new(&self.appearance_select)\n                                    .appearance(false)\n                                    .py_2()\n                                    .pl_3(),\n                            ),\n                        )\n                        .child(Divider::vertical())\n                        .child(\n                            div().flex_1().child(\n                                Input::new(&self.input_state)\n                                    .appearance(false)\n                                    .pr_3()\n                                    .py_2(),\n                            ),\n                        )\n                        .child(\n                            div()\n                                .p_2()\n                                .child(Button::new(\"send\").small().ghost().label(\"Send\")),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Selected Values\").max_w_lg().child(\n                    v_flex()\n                        .gap_3()\n                        .child(format!(\n                            \"Country: {:?}\",\n                            self.country_select.read(cx).selected_value()\n                        ))\n                        .child(format!(\n                            \"fruit: {:?}\",\n                            self.fruit_select.read(cx).selected_value()\n                        ))\n                        .child(format!(\n                            \"UI: {:?}\",\n                            self.simple_select1.read(cx).selected_value()\n                        ))\n                        .child(format!(\n                            \"Language: {:?}\",\n                            self.simple_select2.read(cx).selected_value()\n                        ))\n                        .child(\"This is other text.\"),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/settings_story.rs",
    "content": "use gpui::{\n    App, AppContext, Axis, Context, Element, Entity, FocusHandle, Focusable, Global, IntoElement,\n    ParentElement as _, Render, SharedString, Styled, Window, px,\n};\n\nuse gpui_component::{\n    ActiveTheme, Icon, IconName, Sizable, Size, Theme, ThemeMode,\n    button::Button,\n    group_box::GroupBoxVariant,\n    h_flex,\n    label::Label,\n    setting::{\n        NumberFieldOptions, RenderOptions, SettingField, SettingFieldElement, SettingGroup,\n        SettingItem, SettingPage, Settings,\n    },\n    text::markdown,\n    v_flex,\n};\n\nstruct AppSettings {\n    auto_switch_theme: bool,\n    cli_path: SharedString,\n    font_family: SharedString,\n    font_size: f64,\n    line_height: f64,\n    notifications_enabled: bool,\n    auto_update: bool,\n    resettable: bool,\n}\n\nimpl Default for AppSettings {\n    fn default() -> Self {\n        Self {\n            auto_switch_theme: false,\n            cli_path: \"/usr/local/bin/bash\".into(),\n            font_family: \"Arial\".into(),\n            font_size: 14.0,\n            line_height: 12.0,\n            notifications_enabled: true,\n            auto_update: true,\n            resettable: true,\n        }\n    }\n}\n\nimpl Global for AppSettings {}\n\nimpl AppSettings {\n    fn global(cx: &App) -> &AppSettings {\n        cx.global::<AppSettings>()\n    }\n\n    pub fn global_mut(cx: &mut App) -> &mut AppSettings {\n        cx.global_mut::<AppSettings>()\n    }\n}\n\npub struct SettingsStory {\n    focus_handle: FocusHandle,\n    group_variant: GroupBoxVariant,\n    size: Size,\n}\n\nstruct OpenURLSettingField {\n    label: SharedString,\n    url: SharedString,\n}\n\nimpl OpenURLSettingField {\n    fn new(label: impl Into<SharedString>, url: impl Into<SharedString>) -> Self {\n        Self {\n            label: label.into(),\n            url: url.into(),\n        }\n    }\n}\n\nimpl SettingFieldElement for OpenURLSettingField {\n    type Element = Button;\n    fn render_field(&self, options: &RenderOptions, _: &mut Window, _: &mut App) -> Self::Element {\n        let url = self.url.clone();\n        Button::new(\"open-url\")\n            .outline()\n            .label(self.label.clone())\n            .with_size(options.size)\n            .on_click(move |_, _window, cx| {\n                cx.open_url(url.as_str());\n            })\n    }\n}\n\nimpl super::Story for SettingsStory {\n    fn title() -> &'static str {\n        \"Settings\"\n    }\n\n    fn description() -> &'static str {\n        \"A collection of settings groups and items for the application.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n\n    fn paddings() -> gpui::Pixels {\n        px(0.)\n    }\n}\n\nimpl SettingsStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        cx.set_global::<AppSettings>(AppSettings::default());\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            group_variant: GroupBoxVariant::Outline,\n            size: Size::default(),\n        }\n    }\n\n    fn setting_pages(&self, _: &mut Window, cx: &mut Context<Self>) -> Vec<SettingPage> {\n        let view = cx.entity();\n        let default_settings = AppSettings::default();\n        let resettable = AppSettings::global(cx).resettable;\n\n        vec![\n            SettingPage::new(\"General\")\n                .resettable(resettable)\n                .default_open(true)\n                .icon(Icon::new(IconName::Settings2))\n                .groups(vec![\n                    SettingGroup::new().title(\"Appearance\").items(vec![\n                        SettingItem::new(\n                            \"Dark Mode\",\n                            SettingField::switch(\n                                |cx: &App| cx.theme().mode.is_dark(),\n                                |val: bool, cx: &mut App| {\n                                    let mode = if val {\n                                        ThemeMode::Dark\n                                    } else {\n                                        ThemeMode::Light\n                                    };\n                                    Theme::global_mut(cx).mode = mode;\n                                    Theme::change(mode, None, cx);\n                                },\n                            )\n                            .default_value(false),\n                        )\n                        .description(\"Switch between light and dark themes.\"),\n                        SettingItem::new(\n                            \"Auto Switch Theme\",\n                            SettingField::checkbox(\n                                |cx: &App| AppSettings::global(cx).auto_switch_theme,\n                                |val: bool, cx: &mut App| {\n                                    AppSettings::global_mut(cx).auto_switch_theme = val;\n                                },\n                            )\n                            .default_value(default_settings.auto_switch_theme),\n                        )\n                        .description(\"Automatically switch theme based on system settings.\"),\n                        SettingItem::new(\n                            \"resettable\",\n                            SettingField::switch(\n                                |cx: &App| AppSettings::global(cx).resettable,\n                                |checked: bool, cx: &mut App| {\n                                    AppSettings::global_mut(cx).resettable = checked\n                                },\n                            ),\n                        )\n                        .description(\"Enable/Disable reset button for settings.\"),\n                        SettingItem::new(\n                            \"Group Variant\",\n                            SettingField::dropdown(\n                                vec![\n                                    (GroupBoxVariant::Normal.as_str().into(), \"Normal\".into()),\n                                    (GroupBoxVariant::Outline.as_str().into(), \"Outline\".into()),\n                                    (GroupBoxVariant::Fill.as_str().into(), \"Fill\".into()),\n                                ],\n                                {\n                                    let view = view.clone();\n                                    move |cx: &App| {\n                                        SharedString::from(\n                                            view.read(cx).group_variant.as_str().to_string(),\n                                        )\n                                    }\n                                },\n                                {\n                                    let view = view.clone();\n                                    move |val: SharedString, cx: &mut App| {\n                                        view.update(cx, |view, cx| {\n                                            view.group_variant =\n                                                GroupBoxVariant::from_str(val.as_str());\n                                            cx.notify();\n                                        });\n                                    }\n                                },\n                            )\n                            .default_value(GroupBoxVariant::Outline.as_str().to_string()),\n                        )\n                        .description(\"Select the variant for setting groups.\"),\n                        SettingItem::new(\n                            \"Group Size\",\n                            SettingField::dropdown(\n                                vec![\n                                    (Size::Medium.as_str().into(), \"Medium\".into()),\n                                    (Size::Small.as_str().into(), \"Small\".into()),\n                                    (Size::XSmall.as_str().into(), \"XSmall\".into()),\n                                ],\n                                {\n                                    let view = view.clone();\n                                    move |cx: &App| {\n                                        SharedString::from(view.read(cx).size.as_str().to_string())\n                                    }\n                                },\n                                {\n                                    let view = view.clone();\n                                    move |val: SharedString, cx: &mut App| {\n                                        view.update(cx, |view, cx| {\n                                            view.size = Size::from_str(val.as_str());\n                                            cx.notify();\n                                        });\n                                    }\n                                },\n                            )\n                            .default_value(Size::default().as_str().to_string()),\n                        )\n                        .description(\"Select the size for the setting group.\"),\n                    ]),\n                    SettingGroup::new()\n                        .title(\"Font\")\n                        .item(\n                            SettingItem::new(\n                                \"Font Family\",\n                                SettingField::dropdown(\n                                    vec![\n                                        (\"Arial\".into(), \"Arial\".into()),\n                                        (\"Helvetica\".into(), \"Helvetica\".into()),\n                                        (\"Times New Roman\".into(), \"Times New Roman\".into()),\n                                        (\"Courier New\".into(), \"Courier New\".into()),\n                                    ],\n                                    |cx: &App| AppSettings::global(cx).font_family.clone(),\n                                    |val: SharedString, cx: &mut App| {\n                                        AppSettings::global_mut(cx).font_family = val;\n                                    },\n                                )\n                                .default_value(default_settings.font_family),\n                            )\n                            .description(\"Select the font family for the story.\"),\n                        )\n                        .item(\n                            SettingItem::new(\n                                \"Font Size\",\n                                SettingField::number_input(\n                                    NumberFieldOptions {\n                                        min: 8.0,\n                                        max: 72.0,\n                                        ..Default::default()\n                                    },\n                                    |cx: &App| AppSettings::global(cx).font_size,\n                                    |val: f64, cx: &mut App| {\n                                        AppSettings::global_mut(cx).font_size = val;\n                                    },\n                                )\n                                .default_value(default_settings.font_size),\n                            )\n                            .description(\n                                \"Adjust the font size for better readability between 8 and 72.\",\n                            ),\n                        )\n                        .item(\n                            SettingItem::new(\n                                \"Line Height\",\n                                SettingField::number_input(\n                                    NumberFieldOptions {\n                                        min: 8.0,\n                                        max: 32.0,\n                                        ..Default::default()\n                                    },\n                                    |cx: &App| AppSettings::global(cx).line_height,\n                                    |val: f64, cx: &mut App| {\n                                        AppSettings::global_mut(cx).line_height = val;\n                                    },\n                                )\n                                .default_value(default_settings.line_height),\n                            )\n                            .description(\n                                \"Adjust the line height for better readability between 8 and 32.\",\n                            ),\n                        ),\n                    SettingGroup::new().title(\"Other\").items(vec![\n                        SettingItem::render(|options, _, _| {\n                            h_flex()\n                                .w_full()\n                                .justify_between()\n                                .flex_wrap()\n                                .gap_3()\n                                .child(\"This is a custom element item by use SettingItem::element.\")\n                                .child(\n                                    Button::new(\"action\")\n                                        .icon(IconName::Globe)\n                                        .label(\"Repository...\")\n                                        .outline()\n                                        .with_size(options.size)\n                                        .on_click(|_, _, cx| {\n                                            cx.open_url(\n                                                \"https://github.com/longbridge/gpui-component\",\n                                            );\n                                        }),\n                                )\n                                .into_any_element()\n                        }),\n                        SettingItem::new(\n                            \"CLI Path\",\n                            SettingField::input(\n                                |cx: &App| AppSettings::global(cx).cli_path.clone(),\n                                |val: SharedString, cx: &mut App| {\n                                    println!(\"cli-path set value: {}\", val);\n                                    AppSettings::global_mut(cx).cli_path = val;\n                                },\n                            )\n                            .default_value(default_settings.cli_path),\n                        )\n                        .layout(Axis::Vertical)\n                        .description(\n                            \"Path to the CLI executable. \\n\\\n                        This item uses Vertical layout. The title,\\\n                        description, and field are all aligned vertically with width 100%.\",\n                        ),\n                    ]),\n                ]),\n            SettingPage::new(\"Software Update\")\n                .resettable(resettable)\n                .icon(Icon::new(IconName::Cpu))\n                .groups(vec![SettingGroup::new().title(\"Updates\").items(vec![\n                    SettingItem::new(\n                        \"Enable Notifications\",\n                        SettingField::switch(\n                            |cx: &App| AppSettings::global(cx).notifications_enabled,\n                            |val: bool, cx: &mut App| {\n                                AppSettings::global_mut(cx).notifications_enabled = val;\n                            },\n                        )\n                        .default_value(default_settings.notifications_enabled),\n                    )\n                    .description(\"Receive notifications about updates and news.\"),\n                    SettingItem::new(\n                        \"Auto Update\",\n                        SettingField::switch(\n                            |cx: &App| AppSettings::global(cx).auto_update,\n                            |val: bool, cx: &mut App| {\n                                AppSettings::global_mut(cx).auto_update = val;\n                            },\n                        )\n                        .default_value(default_settings.auto_update),\n                    )\n                    .description(\"Automatically download and install updates.\"),\n                ])]),\n            SettingPage::new(\"About\")\n                .resettable(resettable)\n                .icon(Icon::new(IconName::Info))\n                .group(\n                    SettingGroup::new().item(SettingItem::render(|_options, _, cx| {\n                        v_flex()\n                            .gap_3()\n                            .w_full()\n                            .items_center()\n                            .justify_center()\n                            .child(Icon::new(IconName::GalleryVerticalEnd).size_16())\n                            .child(\"GPUI Component\")\n                            .child(\n                                Label::new(\n                                    \"Rust GUI components for building fantastic cross-platform \\\n                                    desktop application by using GPUI.\",\n                                )\n                                .text_sm()\n                                .text_color(cx.theme().muted_foreground),\n                            )\n                            .into_any()\n                    })),\n                )\n                .group(SettingGroup::new().title(\"Links\").items(vec![\n                        SettingItem::new(\n                            \"GitHub Repository\",\n                            SettingField::element(OpenURLSettingField::new(\n                                \"Repository...\",\n                                \"https://github.com/longbridge/gpui-component\",\n                            )),\n                        )\n                        .description(\"Open the GitHub repository in your default browser.\"),\n                        SettingItem::new(\n                            \"Documentation\",\n                            SettingField::element(OpenURLSettingField::new(\n                                \"Rust Docs...\",\n                                \"https://docs.rs/gpui-component\"\n                            )),\n                        )\n                        .description(markdown(\n                            \"Rust doc for the `gpui-component` crate.\",\n                        )),\n                        SettingItem::new(\n                            \"Website\",\n                            SettingField::render(|options, _window, _cx| {\n                                Button::new(\"open-url\")\n                                    .outline()\n                                    .label(\"Website...\")\n                                    .with_size(options.size)\n                                    .on_click(|_, _window, cx| {\n                                        cx.open_url(\"https://longbridge.github.io/gpui-component/\");\n                                    })\n                            }),\n                        )\n                        .description(\"Official website and documentation for the GPUI Component.\"),\n                    ])),\n        ]\n    }\n}\n\nimpl Focusable for SettingsStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for SettingsStory {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        Settings::new(\"app-settings\")\n            .with_size(self.size)\n            .with_group_variant(self.group_variant)\n            .pages(self.setting_pages(window, cx))\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/sheet_story.rs",
    "content": "use std::{sync::Arc, time::Duration};\n\nuse fake::Fake;\nuse gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, InteractiveElement as _, IntoElement,\n    ParentElement, Render, SharedString, Styled, Task, WeakEntity, Window, div,\n    prelude::FluentBuilder as _, px,\n};\n\nuse gpui_component::{\n    ActiveTheme as _, Icon, IconName, IndexPath, Placement, WindowExt,\n    button::{Button, ButtonVariant, ButtonVariants as _},\n    checkbox::Checkbox,\n    date_picker::{DatePicker, DatePickerState},\n    h_flex,\n    input::{Input, InputState},\n    list::{List, ListDelegate, ListItem, ListState},\n    v_flex,\n};\n\nuse crate::TestAction;\nuse crate::{Story, section};\n\npub struct ListItemDeletegate {\n    story: WeakEntity<SheetStory>,\n    confirmed_index: Option<usize>,\n    selected_index: Option<usize>,\n    items: Vec<Arc<String>>,\n    matches: Vec<Arc<String>>,\n}\n\nimpl ListDelegate for ListItemDeletegate {\n    type Item = ListItem;\n\n    fn items_count(&self, _: usize, _: &App) -> usize {\n        self.matches.len()\n    }\n\n    fn perform_search(\n        &mut self,\n        query: &str,\n        _: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> Task<()> {\n        let query = query.to_string();\n        cx.spawn(async move |this, cx| {\n            // Simulate a slow search.\n            let sleep = (0.05..0.1).fake();\n            cx.background_executor().timer(Duration::from_secs_f64(sleep)).await;\n\n            this.update(cx, |this, cx| {\n                this.delegate_mut().matches = this\n                    .delegate()\n                    .items\n                    .iter()\n                    .filter(|item| item.to_lowercase().contains(&query.to_lowercase()))\n                    .cloned()\n                    .collect();\n                cx.notify();\n            })\n            .ok();\n        })\n    }\n\n    fn render_item(\n        &mut self,\n        ix: IndexPath,\n        _: &mut Window,\n        _: &mut Context<ListState<Self>>,\n    ) -> Option<Self::Item> {\n        let confirmed = Some(ix.row) == self.confirmed_index;\n\n        if let Some(item) = self.matches.get(ix.row) {\n            let list_item = ListItem::new((\"item\", ix.row))\n                .check_icon(IconName::Check)\n                .confirmed(confirmed)\n                .child(h_flex().items_center().justify_between().child(item.to_string()))\n                .suffix(|_, _| {\n                    Button::new(\"like\")\n                        .tab_stop(false)\n                        .icon(IconName::Heart)\n                        .with_variant(ButtonVariant::Ghost)\n                        .size(px(18.))\n                        .on_click(move |_, window, cx| {\n                            cx.stop_propagation();\n                            window.prevent_default();\n\n                            println!(\"You have clicked like.\");\n                        })\n                });\n            Some(list_item)\n        } else {\n            None\n        }\n    }\n\n    fn render_empty(\n        &mut self,\n        _: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> impl IntoElement {\n        v_flex()\n            .size_full()\n            .child(Icon::new(IconName::Inbox).size(px(50.)).text_color(cx.theme().muted_foreground))\n            .child(\"No matches found\")\n            .items_center()\n            .justify_center()\n            .p_3()\n            .bg(cx.theme().muted)\n            .text_color(cx.theme().muted_foreground)\n    }\n\n    fn cancel(&mut self, window: &mut Window, cx: &mut Context<ListState<Self>>) {\n        _ = self.story.update(cx, |this, cx| {\n            this.close_sheet(window, cx);\n        });\n    }\n\n    fn confirm(&mut self, _secondary: bool, _: &mut Window, cx: &mut Context<ListState<Self>>) {\n        _ = self.story.update(cx, |this, _| {\n            self.confirmed_index = self.selected_index;\n            if let Some(ix) = self.confirmed_index {\n                if let Some(item) = self.matches.get(ix) {\n                    this.selected_value = Some(SharedString::from(item.to_string()));\n                }\n            }\n        });\n    }\n\n    fn set_selected_index(\n        &mut self,\n        ix: Option<IndexPath>,\n        _: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) {\n        self.selected_index = ix.map(|ix| ix.row);\n\n        if let Some(_) = ix {\n            cx.notify();\n        }\n    }\n}\n\npub struct SheetStory {\n    focus_handle: FocusHandle,\n    placement: Option<Placement>,\n    selected_value: Option<SharedString>,\n    list: Entity<ListState<ListItemDeletegate>>,\n    input1: Entity<InputState>,\n    input2: Entity<InputState>,\n    date: Entity<DatePickerState>,\n    overlay: bool,\n    overlay_closable: bool,\n}\n\nimpl Story for SheetStory {\n    fn title() -> &'static str {\n        \"Sheet\"\n    }\n\n    fn description() -> &'static str {\n        \"Sheet for open a popup in the edge of the window\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl SheetStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let items: Vec<Arc<String>> = [\n            \"Baguette (France)\",\n            \"Baklava (Turkey)\",\n            \"Beef Wellington (UK)\",\n            \"Biryani (India)\",\n            \"Borscht (Ukraine)\",\n            \"Bratwurst (Germany)\",\n            \"Bulgogi (Korea)\",\n            \"Burrito (USA)\",\n            \"Ceviche (Peru)\",\n            \"Chicken Tikka Masala (India)\",\n            \"Churrasco (Brazil)\",\n            \"Couscous (North Africa)\",\n            \"Croissant (France)\",\n            \"Dim Sum (China)\",\n            \"Empanada (Argentina)\",\n            \"Fajitas (Mexico)\",\n            \"Falafel (Middle East)\",\n            \"Feijoada (Brazil)\",\n            \"Fish and Chips (UK)\",\n            \"Fondue (Switzerland)\",\n            \"Goulash (Hungary)\",\n            \"Haggis (Scotland)\",\n            \"Kebab (Middle East)\",\n            \"Kimchi (Korea)\",\n            \"Lasagna (Italy)\",\n            \"Maple Syrup Pancakes (Canada)\",\n            \"Moussaka (Greece)\",\n            \"Pad Thai (Thailand)\",\n            \"Paella (Spain)\",\n            \"Pancakes (USA)\",\n            \"Pasta Carbonara (Italy)\",\n            \"Pavlova (Australia)\",\n            \"Peking Duck (China)\",\n            \"Pho (Vietnam)\",\n            \"Pierogi (Poland)\",\n            \"Pizza (Italy)\",\n            \"Poutine (Canada)\",\n            \"Pretzel (Germany)\",\n            \"Ramen (Japan)\",\n            \"Rendang (Indonesia)\",\n            \"Sashimi (Japan)\",\n            \"Satay (Indonesia)\",\n            \"Shepherd's Pie (Ireland)\",\n            \"Sushi (Japan)\",\n            \"Tacos (Mexico)\",\n            \"Tandoori Chicken (India)\",\n            \"Tortilla (Spain)\",\n            \"Tzatziki (Greece)\",\n            \"Wiener Schnitzel (Austria)\",\n        ]\n        .iter()\n        .map(|s| Arc::new(s.to_string()))\n        .collect();\n\n        let story = cx.entity().downgrade();\n        let delegate = ListItemDeletegate {\n            story,\n            selected_index: None,\n            confirmed_index: None,\n            items: items.clone(),\n            matches: items.clone(),\n        };\n        let list = cx.new(|cx| ListState::new(delegate, window, cx).searchable(true));\n        let input1 = cx.new(|cx| InputState::new(window, cx).placeholder(\"Your Name\"));\n        let input2 = cx.new(|cx| {\n            InputState::new(window, cx).placeholder(\"For test focus back on dialog close.\")\n        });\n        let date = cx.new(|cx| DatePickerState::new(window, cx));\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            placement: None,\n            selected_value: None,\n            list,\n            input1,\n            input2,\n            date,\n            overlay: true,\n            overlay_closable: true,\n        }\n    }\n\n    fn open_sheet_at(&mut self, placement: Placement, window: &mut Window, cx: &mut Context<Self>) {\n        let list = self.list.clone();\n\n        let drawer_h = match placement {\n            Placement::Left | Placement::Right => px(400.),\n            Placement::Top | Placement::Bottom => px(540.),\n        };\n\n        let overlay = self.overlay;\n        let overlay_closable = self.overlay_closable;\n        let input1 = self.input1.clone();\n        let date = self.date.clone();\n        window.open_sheet_at(placement, cx, move |this, _, cx| {\n            this.overlay(overlay)\n                .overlay_closable(overlay_closable)\n                .size(drawer_h)\n                .title(\"Sheet Title\")\n                .child(\n                    v_flex()\n                        .size_full()\n                        .gap_3()\n                        .child(Input::new(&input1))\n                        .child(DatePicker::new(&date).placeholder(\"Date of Birth\"))\n                        .child(\n                            Button::new(\"send-notification\").child(\"Test Notification\").on_click(\n                                |_, window, cx| {\n                                    window\n                                        .push_notification(\"Hello this is message from Sheet.\", cx)\n                                },\n                            ),\n                        )\n                        .child(\n                            Button::new(\"confirm-dialog-from-sheet\")\n                                .child(\"Open Confirm Dialog\")\n                                .on_click(|_, window, cx| {\n                                    window.open_alert_dialog(cx, move |dialog, _, _| {\n                                        dialog\n                                            .child(\"Confirm dialog opened from sheet.\")\n                                            .on_ok(|_, window, cx| {\n                                                window\n                                                    .push_notification(\"You have pressed ok.\", cx);\n                                                true\n                                            })\n                                            .on_cancel(|_, window, cx| {\n                                                window.push_notification(\n                                                    \"You have pressed cancel.\",\n                                                    cx,\n                                                );\n                                                true\n                                            })\n                                    });\n                                }),\n                        )\n                        .child(\n                            List::new(&list)\n                                .border_1()\n                                .border_color(cx.theme().border)\n                                .rounded(cx.theme().radius),\n                        ),\n                )\n                .footer(\n                    h_flex()\n                        .gap_6()\n                        .items_center()\n                        .child(Button::new(\"confirm\").primary().label(\"Confirm\").on_click(\n                            |_, window, cx| {\n                                window.close_sheet(cx);\n                            },\n                        ))\n                        .child(Button::new(\"cancel\").label(\"Cancel\").on_click(|_, window, cx| {\n                            window.close_sheet(cx);\n                        })),\n                )\n        });\n    }\n\n    fn close_sheet(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        self.placement = None;\n        cx.notify();\n    }\n\n    fn on_action_test_action(\n        &mut self,\n        _: &TestAction,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        window.push_notification(\"You have clicked the TestAction.\", cx);\n    }\n}\n\nimpl Focusable for SheetStory {\n    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for SheetStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .id(\"sheet-story\")\n            .track_focus(&self.focus_handle)\n            .on_action(cx.listener(Self::on_action_test_action))\n            .size_full()\n            .child(\n                v_flex()\n                    .gap_6()\n                    .child(\n                        h_flex()\n                            .id(\"state\")\n                            .items_center()\n                            .gap_3()\n                            .child(\n                                Checkbox::new(\"overlay\")\n                                    .label(\"Overlay\")\n                                    .checked(self.overlay)\n                                    .on_click(cx.listener(|view, _, _, cx| {\n                                        view.overlay = !view.overlay;\n                                        cx.notify();\n                                    })),\n                            )\n                            .child(\n                                Checkbox::new(\"closable\")\n                                    .label(\"Overlay Closable\")\n                                    .checked(self.overlay_closable)\n                                    .on_click(cx.listener(|view, _, _, cx| {\n                                        view.overlay_closable = !view.overlay_closable;\n                                        cx.notify();\n                                    })),\n                            ),\n                    )\n                    .child(\n                        section(\"Normal Sheet\")\n                            .child(\n                                Button::new(\"show-sheet-left\")\n                                    .outline()\n                                    .label(\"Left Sheet...\")\n                                    .on_click(cx.listener(|this, _, window, cx| {\n                                        this.open_sheet_at(Placement::Left, window, cx)\n                                    })),\n                            )\n                            .child(\n                                Button::new(\"show-sheet-top\")\n                                    .outline()\n                                    .label(\"Top Sheet...\")\n                                    .on_click(cx.listener(|this, _, window, cx| {\n                                        this.open_sheet_at(Placement::Top, window, cx)\n                                    })),\n                            )\n                            .child(\n                                Button::new(\"show-sheet-right\")\n                                    .outline()\n                                    .label(\"Right Sheet...\")\n                                    .on_click(cx.listener(|this, _, window, cx| {\n                                        this.open_sheet_at(Placement::Right, window, cx)\n                                    })),\n                            )\n                            .child(\n                                Button::new(\"show-sheet-bottom\")\n                                    .outline()\n                                    .label(\"Bottom Sheet...\")\n                                    .on_click(cx.listener(|this, _, window, cx| {\n                                        this.open_sheet_at(Placement::Bottom, window, cx)\n                                    })),\n                            ),\n                    )\n                    .child(\n                        section(\"Scrollable Sheet\").max_w_md().child(\n                            Button::new(\"show-scrollable-sheet\")\n                                .outline()\n                                .label(\"Scrollable Sheet...\")\n                                .on_click(cx.listener(|_, _, window, cx| {\n                                    window.open_sheet_at(\n                                        Placement::Right,\n                                        cx,\n                                        move |this, _, _| {\n                                            this.title(\"Scrollable Sheet\")\n                                                .child(\"This is a scrollable sheet.\\n\".repeat(150))\n                                        },\n                                    );\n                                })),\n                        ),\n                    )\n                    .child(\n                        section(\"Focus back test\")\n                            .max_w_md()\n                            .child(Input::new(&self.input2))\n                            .child(\n                                Button::new(\"test-action\")\n                                    .outline()\n                                    .label(\"Test Action\")\n                                    .flex_shrink_0()\n                                    .on_click(|_, window, cx| {\n                                        window.dispatch_action(Box::new(TestAction), cx);\n                                    })\n                                    .tooltip(\n                                        \"This button for test dispatch action, \\\n                                        to make sure when Dialog close,\\\n                                        \\nthis still can handle the action.\",\n                                    ),\n                            ),\n                    )\n                    .when_some(self.selected_value.clone(), |this, selected_value| {\n                        this.child(\n                            h_flex().gap_1().child(\"You have selected:\").child(\n                                div().child(selected_value.to_string()).text_color(gpui::red()),\n                            ),\n                        )\n                    }),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/sidebar_story.rs",
    "content": "use std::collections::HashMap;\n\nuse gpui::{\n    Action, App, AppContext, ClickEvent, Context, Entity, Focusable, IntoElement, ParentElement,\n    Render, SharedString, Styled, Window, div, prelude::FluentBuilder, px, relative,\n};\n\nuse gpui_component::{\n    ActiveTheme, Icon, IconName, Side, Sizable,\n    badge::Badge,\n    breadcrumb::{Breadcrumb, BreadcrumbItem},\n    divider::Divider,\n    h_flex,\n    menu::DropdownMenu,\n    sidebar::{\n        Sidebar, SidebarFooter, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem,\n        SidebarToggleButton,\n    },\n    switch::Switch,\n    v_flex,\n};\nuse serde::Deserialize;\n\n#[derive(Action, Clone, PartialEq, Eq, Deserialize)]\n#[action(namespace = sidebar_story, no_json)]\npub struct SelectCompany(SharedString);\n\npub struct SidebarStory {\n    active_items: HashMap<Item, bool>,\n    last_active_item: Item,\n    active_subitem: Option<SubItem>,\n    collapsed: bool,\n    side: Side,\n    click_to_open_submenu: bool,\n    focus_handle: gpui::FocusHandle,\n    checked: bool,\n}\n\nimpl SidebarStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        let mut active_items = HashMap::new();\n        active_items.insert(Item::Playground, true);\n\n        Self {\n            active_items,\n            last_active_item: Item::Playground,\n            active_subitem: None,\n            collapsed: false,\n            side: Side::Left,\n            focus_handle: cx.focus_handle(),\n            checked: false,\n            click_to_open_submenu: false,\n        }\n    }\n\n    fn render_content(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex().gap_3().child(\n            h_flex()\n                .gap_3()\n                .child(\n                    Switch::new(\"side\")\n                        .label(\"Placement Right\")\n                        .checked(self.side.is_right())\n                        .on_click(cx.listener(|this, checked: &bool, _, cx| {\n                            this.side = if *checked { Side::Right } else { Side::Left };\n                            cx.notify();\n                        })),\n                )\n                .child(\n                    Switch::new(\"click-to-open\")\n                        .checked(self.click_to_open_submenu)\n                        .label(\"Click to open submenu\")\n                        .on_click(cx.listener(|this, checked: &bool, _, cx| {\n                            this.click_to_open_submenu = *checked;\n                            cx.notify();\n                        })),\n                ),\n        )\n    }\n\n    fn switch_checked_handler(\n        &mut self,\n        checked: &bool,\n        _: &mut Window,\n        _: &mut Context<SidebarStory>,\n    ) {\n        self.checked = *checked;\n    }\n}\n\n#[derive(Clone, Copy, PartialEq, Eq, Hash)]\nenum Item {\n    Playground,\n    Models,\n    Documentation,\n    Settings,\n    DesignEngineering,\n    SalesAndMarketing,\n    Travel,\n}\n\n#[derive(Clone, Copy, PartialEq, Eq)]\nenum SubItem {\n    History,\n    Starred,\n    General,\n    Team,\n    Billing,\n    Limits,\n    Settings,\n    Genesis,\n    Explorer,\n    Quantum,\n    Introduction,\n    GetStarted,\n    Tutorial,\n    Changelog,\n}\n\nimpl Item {\n    pub fn label(&self) -> &'static str {\n        match self {\n            Self::Playground => \"Playground\",\n            Self::Models => \"Models\",\n            Self::Documentation => \"Documentation\",\n            Self::Settings => \"Settings\",\n            Self::DesignEngineering => \"Design Engineering\",\n            Self::SalesAndMarketing => \"Sales and Marketing\",\n            Self::Travel => \"Travel\",\n        }\n    }\n\n    pub fn is_disabled(&self) -> bool {\n        match self {\n            Self::Travel => true,\n            _ => false,\n        }\n    }\n\n    pub fn icon(&self) -> IconName {\n        match self {\n            Self::Playground => IconName::SquareTerminal,\n            Self::Models => IconName::Bot,\n            Self::Documentation => IconName::BookOpen,\n            Self::Settings => IconName::Settings2,\n            Self::DesignEngineering => IconName::Frame,\n            Self::SalesAndMarketing => IconName::ChartPie,\n            Self::Travel => IconName::Map,\n        }\n    }\n\n    pub fn handler(\n        &self,\n    ) -> impl Fn(&mut SidebarStory, &ClickEvent, &mut Window, &mut Context<SidebarStory>) + 'static\n    {\n        let item = *self;\n        move |this, _, _, cx| {\n            if this.active_items.contains_key(&item) {\n                this.active_items.remove(&item);\n            } else {\n                this.active_items.insert(item, true);\n            }\n\n            this.last_active_item = item;\n            this.active_subitem = None;\n            cx.notify();\n        }\n    }\n\n    pub fn items(&self) -> Vec<SubItem> {\n        match self {\n            Self::Playground => vec![SubItem::History, SubItem::Starred, SubItem::Settings],\n            Self::Models => vec![SubItem::Genesis, SubItem::Explorer, SubItem::Quantum],\n            Self::Documentation => vec![\n                SubItem::Introduction,\n                SubItem::GetStarted,\n                SubItem::Tutorial,\n                SubItem::Changelog,\n            ],\n            Self::Settings => vec![\n                SubItem::General,\n                SubItem::Team,\n                SubItem::Billing,\n                SubItem::Limits,\n            ],\n            _ => Vec::new(),\n        }\n    }\n}\n\nimpl SubItem {\n    pub fn label(&self) -> &'static str {\n        match self {\n            Self::History => \"History\",\n            Self::Starred => \"Starred\",\n            Self::Settings => \"Settings\",\n            Self::Genesis => \"Genesis\",\n            Self::Explorer => \"Explorer\",\n            Self::Quantum => \"Quantum\",\n            Self::Introduction => \"Introduction\",\n            Self::GetStarted => \"Get Started\",\n            Self::Tutorial => \"Tutorial\",\n            Self::Changelog => \"Changelog\",\n            Self::Team => \"Team\",\n            Self::Billing => \"Billing\",\n            Self::Limits => \"Limits\",\n            Self::General => \"General\",\n        }\n    }\n\n    pub fn is_disabled(&self) -> bool {\n        match self {\n            Self::Quantum => true,\n            _ => false,\n        }\n    }\n\n    pub fn handler(\n        &self,\n        item: &Item,\n    ) -> impl Fn(&mut SidebarStory, &ClickEvent, &mut Window, &mut Context<SidebarStory>) + 'static\n    {\n        let item = *item;\n        let subitem = *self;\n        move |this, _, _, cx| {\n            println!(\n                \"Clicked on item: {}, child: {}\",\n                item.label(),\n                subitem.label()\n            );\n            this.active_items.insert(item, true);\n            this.last_active_item = item;\n            this.active_subitem = Some(subitem);\n            cx.notify();\n        }\n    }\n}\n\nimpl super::Story for SidebarStory {\n    fn title() -> &'static str {\n        \"Sidebar\"\n    }\n\n    fn description() -> &'static str {\n        \"A composable, themeable and customizable sidebar component.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl Focusable for SidebarStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for SidebarStory {\n    fn render(\n        &mut self,\n        window: &mut gpui::Window,\n        cx: &mut gpui::Context<Self>,\n    ) -> impl gpui::IntoElement {\n        let groups: [Vec<Item>; 2] = [\n            vec![\n                Item::Playground,\n                Item::Models,\n                Item::Documentation,\n                Item::Settings,\n            ],\n            vec![\n                Item::DesignEngineering,\n                Item::SalesAndMarketing,\n                Item::Travel,\n            ],\n        ];\n\n        h_flex()\n            .rounded(cx.theme().radius)\n            .border_1()\n            .border_color(cx.theme().border)\n            .h_full()\n            .when(self.side.is_right(), |this| this.flex_row_reverse())\n            .child(\n                Sidebar::new(\"sidebar-story\")\n                    .side(self.side)\n                    .collapsed(self.collapsed)\n                    .w(px(220.))\n                    .gap_0()\n                    .header(\n                        SidebarHeader::new()\n                            .child(\n                                div()\n                                    .flex()\n                                    .items_center()\n                                    .justify_center()\n                                    .rounded(cx.theme().radius)\n                                    .bg(cx.theme().success)\n                                    .text_color(cx.theme().success_foreground)\n                                    .size_8()\n                                    .flex_shrink_0()\n                                    .when(!self.collapsed, |this| {\n                                        this.child(Icon::new(IconName::GalleryVerticalEnd))\n                                    })\n                                    .when(self.collapsed, |this| {\n                                        this.size_4()\n                                            .bg(cx.theme().transparent)\n                                            .text_color(cx.theme().foreground)\n                                            .child(Icon::new(IconName::GalleryVerticalEnd))\n                                    }),\n                            )\n                            .when(!self.collapsed, |this| {\n                                this.child(\n                                    v_flex()\n                                        .gap_0()\n                                        .text_sm()\n                                        .flex_1()\n                                        .line_height(relative(1.25))\n                                        .overflow_hidden()\n                                        .text_ellipsis()\n                                        .child(\"Company Name\")\n                                        .child(div().child(\"Enterprise\").text_xs()),\n                                )\n                            })\n                            .when(!self.collapsed, |this| {\n                                this.child(\n                                    Icon::new(IconName::ChevronsUpDown).size_4().flex_shrink_0(),\n                                )\n                            })\n                            .dropdown_menu(|menu, _, _| {\n                                menu.menu(\n                                    \"Twitter Inc.\",\n                                    Box::new(SelectCompany(SharedString::from(\"twitter\"))),\n                                )\n                                .menu(\n                                    \"Meta Platforms\",\n                                    Box::new(SelectCompany(SharedString::from(\"meta\"))),\n                                )\n                                .menu(\n                                    \"Google Inc.\",\n                                    Box::new(SelectCompany(SharedString::from(\"google\"))),\n                                )\n                            }),\n                    )\n                    .child(\n                        SidebarGroup::new(\"Platform\").child(SidebarMenu::new().children(\n                            groups[0].iter().enumerate().map(|(ix, item)| {\n                                let is_active =\n                                    self.last_active_item == *item && self.active_subitem == None;\n                                SidebarMenuItem::new(item.label())\n                                    .icon(item.icon())\n                                    .active(is_active)\n                                    .default_open(ix == 0)\n                                    .click_to_open(self.click_to_open_submenu)\n                                    .when(ix == 0, |this| {\n                                        this.context_menu({\n                                            move |this, _, _| {\n                                                this.link(\n                                                    \"About\",\n                                                    \"https://github.com/longbridge/gpui-component\",\n                                                )\n                                            }\n                                        })\n                                    })\n                                    .children(item.items().into_iter().enumerate().map(\n                                        |(ix, sub_item)| {\n                                            SidebarMenuItem::new(sub_item.label())\n                                                .active(self.active_subitem == Some(sub_item))\n                                                .disable(sub_item.is_disabled())\n                                                .when(ix == 0, |this| {\n                                                    this.suffix({\n                                                        let checked = self.checked;\n                                                        let view = cx.entity();\n                                                        move |window, _| {\n                                                            Switch::new(\"switch\")\n                                                                .xsmall()\n                                                                .checked(checked)\n                                                                .on_click(window.listener_for(\n                                                                    &view,\n                                                                    Self::switch_checked_handler,\n                                                                ))\n                                                        }\n                                                    })\n                                                    .context_menu({\n                                                        move |this, _, _| {\n                                                            this.label(\"This is a label\")\n                                                        }\n                                                    })\n                                                })\n                                                .on_click(cx.listener(sub_item.handler(&item)))\n                                        },\n                                    ))\n                                    .on_click(cx.listener(item.handler()))\n                            }),\n                        )),\n                    )\n                    .child(\n                        SidebarGroup::new(\"Projects\").child(SidebarMenu::new().children(\n                            groups[1].iter().enumerate().map(|(ix, item)| {\n                                let is_active =\n                                    self.last_active_item == *item && self.active_subitem == None;\n                                SidebarMenuItem::new(item.label())\n                                    .icon(item.icon())\n                                    .active(is_active)\n                                    .disable(item.is_disabled())\n                                    .click_to_open(self.click_to_open_submenu)\n                                    .when(ix == 0, |this| {\n                                        this.suffix(|_, _| {\n                                            Badge::new().dot().count(1).child(\n                                                div().p_0p5().child(Icon::new(IconName::Bell)),\n                                            )\n                                        })\n                                    })\n                                    .when(ix == 1, |this| {\n                                        this.suffix(|_, _| Icon::new(IconName::Settings2))\n                                    })\n                                    .on_click(cx.listener(item.handler()))\n                            }),\n                        )),\n                    )\n                    .footer(\n                        SidebarFooter::new()\n                            .justify_between()\n                            .child(\n                                h_flex()\n                                    .gap_2()\n                                    .child(IconName::CircleUser)\n                                    .when(!self.collapsed, |this| this.child(\"Jason Lee\")),\n                            )\n                            .when(!self.collapsed, |this| {\n                                this.child(Icon::new(IconName::ChevronsUpDown).size_4())\n                            }),\n                    ),\n            )\n            .child(\n                v_flex()\n                    .size_full()\n                    .gap_4()\n                    .p_4()\n                    .child(\n                        h_flex()\n                            .items_center()\n                            .gap_3()\n                            .when(self.side.is_right(), |this| {\n                                this.flex_row_reverse().justify_between()\n                            })\n                            .child(\n                                SidebarToggleButton::new()\n                                    .side(self.side)\n                                    .collapsed(self.collapsed)\n                                    .on_click(cx.listener(|this, _, _, cx| {\n                                        this.collapsed = !this.collapsed;\n                                        cx.notify();\n                                    })),\n                            )\n                            .child(Divider::vertical().h_4())\n                            .child(\n                                Breadcrumb::new()\n                                    .child(\"Breadcrumb\")\n                                    .child(BreadcrumbItem::new(\"Home\").on_click(cx.listener(\n                                        |this, _, _, cx| {\n                                            this.last_active_item = Item::Playground;\n                                            cx.notify();\n                                        },\n                                    )))\n                                    .child(\n                                        BreadcrumbItem::new(self.last_active_item.label())\n                                            .on_click(cx.listener(|this, _, _, cx| {\n                                                this.active_subitem = None;\n                                                cx.notify();\n                                            })),\n                                    )\n                                    .when_some(self.active_subitem, |this, subitem| {\n                                        this.child(BreadcrumbItem::new(subitem.label()))\n                                    }),\n                            ),\n                    )\n                    .child(self.render_content(window, cx)),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/skeleton_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled,\n    Window, px,\n};\nuse gpui_component::{ActiveTheme as _, skeleton::Skeleton, v_flex};\n\nuse crate::section;\n\npub struct SkeletonStory {\n    focus_handle: gpui::FocusHandle,\n    value: f32,\n}\n\nimpl super::Story for SkeletonStory {\n    fn title() -> &'static str {\n        \"Skeleton\"\n    }\n\n    fn description() -> &'static str {\n        \"Use to show a placeholder while content is loading.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl SkeletonStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            value: 50.,\n        }\n    }\n\n    pub fn set_value(&mut self, value: f32) {\n        self.value = value;\n    }\n}\n\nimpl Focusable for SkeletonStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for SkeletonStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .w_full()\n            .gap_3()\n            .child(\n                section(\"Skeleton\")\n                    .max_w_md()\n                    .child(Skeleton::new().size_12().rounded_full())\n                    .child(\n                        v_flex()\n                            .gap_2()\n                            .child(Skeleton::new().w(px(250.)).h_4().rounded(cx.theme().radius))\n                            .child(Skeleton::new().w(px(200.)).h_4().rounded(cx.theme().radius)),\n                    ),\n            )\n            .child(\n                section(\"Card\").max_w_md().child(\n                    v_flex()\n                        .gap_2()\n                        .child(\n                            Skeleton::new()\n                                .w(px(250.))\n                                .h(px(125.))\n                                .rounded(cx.theme().radius),\n                        )\n                        .child(\n                            v_flex()\n                                .gap_2()\n                                .child(Skeleton::new().w(px(250.)).h_4().rounded(cx.theme().radius))\n                                .child(\n                                    Skeleton::new().w(px(200.)).h_4().rounded(cx.theme().radius),\n                                ),\n                        ),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/slider_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, Hsla, IntoElement, ParentElement, Render,\n    SharedString, Styled, Subscription, Window, hsla, px,\n};\nuse gpui_component::{\n    ActiveTheme, Colorize as _, StyledExt, WindowExt,\n    checkbox::Checkbox,\n    clipboard::Clipboard,\n    h_flex,\n    slider::{Slider, SliderEvent, SliderScale, SliderState},\n    v_flex,\n};\n\nuse crate::section;\n\npub struct SliderStory {\n    focus_handle: gpui::FocusHandle,\n    slider1: Entity<SliderState>,\n    slider1_value: f32,\n    slider2: Entity<SliderState>,\n    slider2_value: f32,\n    slider3: Entity<SliderState>,\n    slider_hsl: [Entity<SliderState>; 4],\n    slider_hsl_value: Hsla,\n    slider4: Entity<SliderState>,\n    slider_logarithmic: Entity<SliderState>,\n    disabled: bool,\n    _subscritions: Vec<Subscription>,\n}\n\nimpl super::Story for SliderStory {\n    fn title() -> &'static str {\n        \"Slider\"\n    }\n\n    fn description() -> &'static str {\n        \"Displays a slider control for selecting a value within a range.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl SliderStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        let slider1 = cx.new(|_| {\n            SliderState::new()\n                .min(-255.)\n                .max(255.)\n                .default_value(75.)\n                .step(15.)\n        });\n\n        let slider2 = cx.new(|_| {\n            SliderState::new()\n                .min(0.)\n                .max(5.)\n                .step(1.0)\n                .default_value(2.)\n        });\n        let slider_hsl = [\n            cx.new(|_| {\n                SliderState::new()\n                    .min(0.)\n                    .max(1.)\n                    .step(0.01)\n                    .default_value(0.38)\n            }),\n            cx.new(|_| {\n                SliderState::new()\n                    .min(0.)\n                    .max(1.)\n                    .step(0.01)\n                    .default_value(0.5)\n            }),\n            cx.new(|_| {\n                SliderState::new()\n                    .min(0.)\n                    .max(1.)\n                    .step(0.01)\n                    .default_value(0.5)\n            }),\n            cx.new(|_| {\n                SliderState::new()\n                    .min(0.)\n                    .max(1.)\n                    .step(0.01)\n                    .default_value(0.5)\n            }),\n        ];\n\n        let slider3 = cx.new(|_| {\n            SliderState::new()\n                .min(0.)\n                .max(100.)\n                .default_value(12.0..45.0)\n                .step(1.)\n        });\n\n        let slider4 = cx.new(|_| {\n            SliderState::new()\n                .min(0.)\n                .max(360.)\n                .default_value(100.0..300.0)\n                .step(1.)\n        });\n\n        let slider_logarithmic = cx.new(|_| {\n            SliderState::new()\n                .min(0.25)\n                .max(4.0)\n                .default_value(1.0)\n                .step(0.05)\n                .scale(SliderScale::Logarithmic)\n        });\n\n        let mut _subscritions = vec![\n            cx.subscribe(&slider1, |this, _, event: &SliderEvent, cx| match event {\n                SliderEvent::Change(value) => {\n                    this.slider1_value = value.start();\n                    cx.notify();\n                }\n            }),\n            cx.subscribe(&slider2, |this, _, event: &SliderEvent, cx| match event {\n                SliderEvent::Change(value) => {\n                    this.slider2_value = value.start();\n                    cx.notify();\n                }\n            }),\n        ];\n\n        _subscritions.extend(\n            slider_hsl\n                .iter()\n                .map(|slider| {\n                    cx.subscribe(slider, |this, _, event: &SliderEvent, cx| match event {\n                        SliderEvent::Change(_) => {\n                            this.slider_hsl_value = hsla(\n                                this.slider_hsl[0].read(cx).value().start(),\n                                this.slider_hsl[1].read(cx).value().start(),\n                                this.slider_hsl[2].read(cx).value().start(),\n                                this.slider_hsl[3].read(cx).value().start(),\n                            );\n                            cx.notify();\n                        }\n                    })\n                })\n                .collect::<Vec<_>>(),\n        );\n\n        slider_hsl[0].update(cx, |slider, cx| {\n            cx.emit(SliderEvent::Change(slider.value()));\n        });\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            slider1_value: 0.,\n            slider2_value: 0.,\n            slider1,\n            slider2,\n            slider3,\n            slider4,\n            slider_hsl,\n            slider_hsl_value: gpui::red(),\n            slider_logarithmic,\n            disabled: false,\n            _subscritions,\n        }\n    }\n}\n\nimpl Focusable for SliderStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for SliderStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let rgb = SharedString::from(self.slider_hsl_value.to_hex());\n\n        v_flex()\n            .w_full()\n            .gap_3()\n            .child(\n                h_flex().child(\n                    Checkbox::new(\"disabled\")\n                        .checked(self.disabled)\n                        .label(\"Disabled\")\n                        .on_click(cx.listener(|this, check: &bool, _, cx| {\n                            this.disabled = *check;\n                            cx.notify();\n                        })),\n                ),\n            )\n            .child(\n                section(\"Horizontal Slider\")\n                    .max_w_md()\n                    .v_flex()\n                    .child(Slider::new(&self.slider1).disabled(self.disabled))\n                    .child(format!(\"Value: {}\", self.slider1_value)),\n            )\n            .child(\n                section(\"Slider (0 - 5) and with color\")\n                    .max_w_md()\n                    .v_flex()\n                    .child(\n                        Slider::new(&self.slider2)\n                            .disabled(self.disabled)\n                            .bg(cx.theme().success)\n                            .text_color(cx.theme().success_foreground),\n                    )\n                    .child(format!(\"Value: {}\", self.slider2_value)),\n            )\n            .child(\n                section(\"Range Mode\")\n                    .max_w_md()\n                    .v_flex()\n                    .child(Slider::new(&self.slider3).disabled(self.disabled))\n                    .child(format!(\"Value: {}\", self.slider3.read(cx).value())),\n            )\n            .child(\n                section(\"Vertical with Range\")\n                    .max_w_md()\n                    .v_flex()\n                    .child(\n                        Slider::new(&self.slider4)\n                            .vertical()\n                            .h(px(200.))\n                            .rounded(px(2.))\n                            .disabled(self.disabled),\n                    )\n                    .child(format!(\"Value: {}\", self.slider4.read(cx).value())),\n            )\n            .child(\n                section(\"Color Picker\")\n                    .sub_title(\n                        h_flex()\n                            .gap_2()\n                            .items_center()\n                            .child(\n                                h_flex()\n                                    .text_color(self.slider_hsl_value)\n                                    .child(rgb.clone()),\n                            )\n                            .child(Clipboard::new(\"copy-hsl\").value(rgb).on_copied(\n                                |_, window, cx| {\n                                    window.push_notification(\"Color copied to clipboard.\", cx)\n                                },\n                            )),\n                    )\n                    .max_w_md()\n                    .justify_around()\n                    .child(\n                        v_flex()\n                            .h_32()\n                            .gap_3()\n                            .items_center()\n                            .justify_center()\n                            .child(\n                                Slider::new(&self.slider_hsl[0])\n                                    .vertical()\n                                    .disabled(self.disabled),\n                            )\n                            .child(\n                                v_flex()\n                                    .items_center()\n                                    .child(\"Hue\")\n                                    .child(format!(\"{:.0}\", self.slider_hsl_value.h * 360.)),\n                            ),\n                    )\n                    .child(\n                        v_flex()\n                            .h_32()\n                            .gap_3()\n                            .items_center()\n                            .justify_center()\n                            .child(\n                                Slider::new(&self.slider_hsl[1])\n                                    .vertical()\n                                    .disabled(self.disabled),\n                            )\n                            .child(\n                                v_flex()\n                                    .items_center()\n                                    .child(\"Saturation\")\n                                    .child(format!(\"{:.0}\", self.slider_hsl_value.s * 100.)),\n                            ),\n                    )\n                    .child(\n                        v_flex()\n                            .h_32()\n                            .gap_3()\n                            .items_center()\n                            .justify_center()\n                            .child(\n                                Slider::new(&self.slider_hsl[2])\n                                    .vertical()\n                                    .disabled(self.disabled),\n                            )\n                            .child(\n                                v_flex()\n                                    .items_center()\n                                    .child(\"Lightness\")\n                                    .child(format!(\"{:.0}\", self.slider_hsl_value.l * 100.)),\n                            ),\n                    )\n                    .child(\n                        v_flex()\n                            .h_32()\n                            .gap_3()\n                            .items_center()\n                            .justify_center()\n                            .child(\n                                Slider::new(&self.slider_hsl[3])\n                                    .vertical()\n                                    .disabled(self.disabled),\n                            )\n                            .child(\n                                v_flex()\n                                    .items_center()\n                                    .child(\"Alpha\")\n                                    .child(format!(\"{:.0}\", self.slider_hsl_value.a * 100.)),\n                            ),\n                    ),\n            )\n            .child(\n                section(\"Logarithmic Slider\")\n                    .max_w_md()\n                    .v_flex()\n                    .child(\n                        Slider::new(&self.slider_logarithmic)\n                            .horizontal()\n                            .disabled(self.disabled),\n                    )\n                    .child(format!(\n                        \"Playback Speed: {:.2}\",\n                        self.slider_logarithmic.read(cx).value().start()\n                    )),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/spinner_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled,\n    Window, px,\n};\nuse gpui_component::{ActiveTheme as _, IconName, Sizable, spinner::Spinner, v_flex};\n\nuse crate::section;\n\npub struct SpinnerStory {\n    focus_handle: gpui::FocusHandle,\n    value: f32,\n}\n\nimpl super::Story for SpinnerStory {\n    fn title() -> &'static str {\n        \"Spinner\"\n    }\n\n    fn description() -> &'static str {\n        \"Displays an spinner showing the completion progress of a task.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl SpinnerStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            value: 50.,\n        }\n    }\n\n    pub fn set_value(&mut self, value: f32) {\n        self.value = value;\n    }\n}\n\nimpl Focusable for SpinnerStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for SpinnerStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .w_full()\n            .gap_3()\n            .child(section(\"Spinner\").gap_x_2().child(Spinner::new()))\n            .child(\n                section(\"Spinner with color\")\n                    .gap_x_2()\n                    .child(Spinner::new().color(cx.theme().blue))\n                    .child(Spinner::new().color(cx.theme().green)),\n            )\n            .child(\n                section(\"Spinner with size\")\n                    .gap_x_2()\n                    .child(Spinner::new().with_size(px(64.)))\n                    .child(Spinner::new().large())\n                    .child(Spinner::new())\n                    .child(Spinner::new().small())\n                    .child(Spinner::new().xsmall()),\n            )\n            .child(\n                section(\"Spinner with Icon\")\n                    .gap_x_2()\n                    .child(Spinner::new().icon(IconName::LoaderCircle))\n                    .child(\n                        Spinner::new()\n                            .icon(IconName::LoaderCircle)\n                            .large()\n                            .color(cx.theme().cyan),\n                    ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/stepper_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled,\n    Subscription, Window,\n};\nuse gpui_component::{\n    IconName, Selectable as _, Sizable, Size, StyledExt,\n    button::{Button, ButtonGroup},\n    checkbox::Checkbox,\n    h_flex,\n    stepper::{Stepper, StepperItem},\n    v_flex,\n};\n\nuse crate::section;\n\npub struct StepperStory {\n    focus_handle: gpui::FocusHandle,\n    size: Size,\n    stepper0_step: usize,\n    stepper1_step: usize,\n    stepper2_step: usize,\n    stepper3_step: usize,\n    disabled: bool,\n    _subscritions: Vec<Subscription>,\n}\n\nimpl super::Story for StepperStory {\n    fn title() -> &'static str {\n        \"Stepper\"\n    }\n\n    fn description() -> &'static str {\n        \"A step-by-step process for users to navigate through a series of steps.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl StepperStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            size: Size::default(),\n            stepper0_step: 1,\n            stepper1_step: 0,\n            stepper2_step: 2,\n            stepper3_step: 0,\n            disabled: false,\n            _subscritions: vec![],\n        }\n    }\n}\n\nimpl Focusable for StepperStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for StepperStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .w_full()\n            .gap_3()\n            .child(\n                h_flex()\n                    .gap_3()\n                    .child(\n                        ButtonGroup::new(\"toggle-size\")\n                            .outline()\n                            .compact()\n                            .child(\n                                Button::new(\"xsmall\")\n                                    .label(\"XSmall\")\n                                    .selected(self.size == Size::XSmall),\n                            )\n                            .child(\n                                Button::new(\"small\")\n                                    .label(\"Small\")\n                                    .selected(self.size == Size::Small),\n                            )\n                            .child(\n                                Button::new(\"medium\")\n                                    .label(\"Medium\")\n                                    .selected(self.size == Size::Medium),\n                            )\n                            .child(\n                                Button::new(\"large\")\n                                    .label(\"Large\")\n                                    .selected(self.size == Size::Large),\n                            )\n                            .on_click(cx.listener(|this, selecteds: &Vec<usize>, _, cx| {\n                                let size = match selecteds[0] {\n                                    0 => Size::XSmall,\n                                    1 => Size::Small,\n                                    2 => Size::Medium,\n                                    3 => Size::Large,\n                                    _ => unreachable!(),\n                                };\n                                this.size = size;\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"disabled\")\n                            .checked(self.disabled)\n                            .label(\"Disabled\")\n                            .on_click(cx.listener(|this, check: &bool, _, cx| {\n                                this.disabled = *check;\n                                cx.notify();\n                            })),\n                    ),\n            )\n            .child(\n                section(\"Horizontal Stepper\").max_w_md().v_flex().child(\n                    Stepper::new(\"stepper0\")\n                        .w_full()\n                        .with_size(self.size)\n                        .disabled(self.disabled)\n                        .selected_index(self.stepper0_step)\n                        .items([\n                            StepperItem::new().child(\"Step 1\"),\n                            StepperItem::new().child(\"Step 2\"),\n                            StepperItem::new().child(\"Step 3\"),\n                        ])\n                        .on_click(cx.listener(|this, step, _, cx| {\n                            this.stepper0_step = *step;\n                            cx.notify();\n                        })),\n                ),\n            )\n            .child(\n                section(\"Icon Stepper\").max_w_md().v_flex().child(\n                    Stepper::new(\"stepper1\")\n                        .w_full()\n                        .with_size(self.size)\n                        .disabled(self.disabled)\n                        .selected_index(self.stepper1_step)\n                        .items([\n                            StepperItem::new()\n                                .icon(IconName::Calendar)\n                                .child(\"Order Details\"),\n                            StepperItem::new().icon(IconName::Inbox).child(\"Shipping\"),\n                            StepperItem::new().icon(IconName::Frame).child(\"Preview\"),\n                            StepperItem::new().icon(IconName::Info).child(\"Finish\"),\n                        ])\n                        .on_click(cx.listener(|this, step, _, cx| {\n                            this.stepper1_step = *step;\n                            cx.notify();\n                        })),\n                ),\n            )\n            .child(\n                section(\"Vertical Stepper\").max_w_md().v_flex().child(\n                    Stepper::new(\"stepper3\")\n                        .vertical()\n                        .with_size(self.size)\n                        .disabled(self.disabled)\n                        .selected_index(self.stepper2_step)\n                        .items_center()\n                        .items([\n                            StepperItem::new()\n                                .pb_8()\n                                .icon(IconName::Building2)\n                                .child(v_flex().child(\"Step 1\").child(\"Description for step 1.\")),\n                            StepperItem::new()\n                                .pb_8()\n                                .icon(IconName::Asterisk)\n                                .child(v_flex().child(\"Step 2\").child(\"Description for step 2.\")),\n                            StepperItem::new()\n                                .pb_8()\n                                .icon(IconName::Folder)\n                                .child(v_flex().child(\"Step 3\").child(\"Description for step 3.\")),\n                            StepperItem::new()\n                                .icon(IconName::CircleCheck)\n                                .child(v_flex().child(\"Step 4\").child(\"Description for step 4.\")),\n                        ])\n                        .on_click(cx.listener(|this, step, _, cx| {\n                            this.stepper2_step = *step;\n                            cx.notify();\n                        })),\n                ),\n            )\n            .child(\n                section(\"Text Center\").max_w_md().v_flex().child(\n                    Stepper::new(\"stepper4\")\n                        .with_size(self.size)\n                        .disabled(self.disabled)\n                        .selected_index(self.stepper3_step)\n                        .text_center(true)\n                        .items([\n                            StepperItem::new().child(\n                                v_flex()\n                                    .items_center()\n                                    .child(\"Step 1\")\n                                    .child(\"Desc for step 1.\"),\n                            ),\n                            StepperItem::new().child(\n                                v_flex()\n                                    .items_center()\n                                    .child(\"Step 2\")\n                                    .child(\"Desc for step 2.\"),\n                            ),\n                            StepperItem::new().child(\n                                v_flex()\n                                    .items_center()\n                                    .child(\"Step 3\")\n                                    .child(\"Desc for step 3.\"),\n                            ),\n                        ])\n                        .on_click(cx.listener(|this, step, _, cx| {\n                            this.stepper3_step = *step;\n                            cx.notify();\n                        })),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/switch_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Div, Entity, FocusHandle, Focusable, IntoElement, ParentElement,\n    Render, SharedString, Styled, Window, px,\n};\n\nuse gpui_component::{\n    ActiveTheme, Disableable as _, Sizable, h_flex, label::Label, switch::Switch, v_flex,\n};\n\nuse crate::section;\n\npub struct SwitchStory {\n    focus_handle: FocusHandle,\n    switch1: bool,\n    switch2: bool,\n    switch3: bool,\n}\n\nimpl super::Story for SwitchStory {\n    fn title() -> &'static str {\n        \"Switch\"\n    }\n\n    fn description() -> &'static str {\n        \"A control that allows the user to toggle between checked and not checked.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl SwitchStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            switch1: true,\n            switch2: false,\n            switch3: true,\n        }\n    }\n}\n\nimpl Focusable for SwitchStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for SwitchStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let theme = cx.theme();\n\n        fn title(title: impl Into<SharedString>) -> Div {\n            v_flex().flex_1().gap_2().child(Label::new(title).text_xl())\n        }\n\n        fn card(cx: &Context<SwitchStory>) -> Div {\n            h_flex()\n                .items_center()\n                .gap_4()\n                .p_4()\n                .w_full()\n                .rounded(cx.theme().radius)\n                .border_1()\n                .border_color(cx.theme().border)\n        }\n\n        v_flex()\n            .w_full()\n            .gap_3()\n            .child(\n                card(cx)\n                    .child(\n                        title(\"Marketing emails\").child(\n                            Label::new(\"Receive emails about new products, features, and more.\")\n                                .text_color(theme.muted_foreground),\n                        ),\n                    )\n                    .child(\n                        h_flex().gap_2().child(\"Subscribe\").child(\n                            Switch::new(\"switch1\")\n                                .checked(self.switch1)\n                                .on_click(cx.listener(move |view, checked, _, cx| {\n                                    view.switch1 = *checked;\n                                    cx.notify();\n                                })),\n                        ),\n                    ),\n            )\n            .child(\n                card(cx)\n                    .child(\n                        title(\"Security emails\").child(\n                            Label::new(\n                                \"Receive emails about your account security. \\\n                                    When turn off, you never receive email again.\",\n                            )\n                            .text_color(theme.muted_foreground),\n                        ),\n                    )\n                    .child(\n                        Switch::new(\"switch2\")\n                            .checked(self.switch2)\n                            .on_click(cx.listener(move |view, checked, _, cx| {\n                                view.switch2 = *checked;\n                                cx.notify();\n                            })),\n                    ),\n            )\n            .child(\n                section(\"Disabled\")\n                    .child(Switch::new(\"switch3\").disabled(true).on_click(|v, _, _| {\n                        println!(\"Switch value changed: {:?}\", v);\n                    }))\n                    .child(\n                        Switch::new(\"switch3_1\")\n                            .w(px(200.))\n                            .label(\"Airplane Mode\")\n                            .checked(true)\n                            .disabled(true)\n                            .on_click(|ev, _, _| {\n                                println!(\"Switch value changed: {:?}\", ev);\n                            }),\n                    ),\n            )\n            .child(\n                section(\"Small Size\").child(\n                    Switch::new(\"switch3\")\n                        .checked(self.switch3)\n                        .label(\"Small Size\")\n                        .small()\n                        .on_click(cx.listener(move |view, checked, _, cx| {\n                            view.switch3 = *checked;\n                            cx.notify();\n                        })),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/table_story.rs",
    "content": "use gpui::{\n    App, AppContext as _, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement,\n    Render, Styled, Window, prelude::FluentBuilder as _, px,\n};\nuse gpui_component::{\n    ActiveTheme, Selectable as _, Sizable, Size,\n    button::{Button, ButtonGroup},\n    h_flex,\n    table::{\n        Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow,\n    },\n    tag::Tag,\n    v_flex,\n};\n\nuse crate::section;\n\npub struct TableStory {\n    focus_handle: FocusHandle,\n    size: Size,\n}\n\nimpl TableStory {\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            size: Size::default(),\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn set_size(&mut self, size: Size, _: &mut Window, cx: &mut Context<Self>) {\n        self.size = size;\n        cx.notify();\n    }\n}\n\nimpl super::Story for TableStory {\n    fn title() -> &'static str {\n        \"Table\"\n    }\n\n    fn description() -> &'static str {\n        \"A basic table component for directly rendering tabular data.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl Focusable for TableStory {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nfn status_tag(status: &str) -> Tag {\n    match status {\n        \"Paid\" => Tag::success().outline().child(status.to_string()),\n        \"Pending\" => Tag::warning().outline().child(status.to_string()),\n        \"Unpaid\" => Tag::danger().outline().child(status.to_string()),\n        _ => Tag::new().child(status.to_string()),\n    }\n    .xsmall()\n}\n\nimpl Render for TableStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let invoices: Vec<(&str, &str, &str, &str, &str)> = vec![\n            (\"INV001\", \"Paid\", \"Credit Card\", \"$250.00\", \"2024-01-15\"),\n            (\"INV002\", \"Pending\", \"PayPal\", \"$150.00\", \"2024-02-01\"),\n            (\"INV003\", \"Unpaid\", \"Bank Transfer\", \"$350.00\", \"2024-02-15\"),\n            (\n                \"INV004\",\n                \"Paid\",\n                \"Credit Card\\nMaster Card / Visa\",\n                \"$450.00\",\n                \"2024-03-01\",\n            ),\n            (\"INV005\", \"Paid\", \"PayPal\", \"$550.00\", \"2024-03-15\"),\n            (\n                \"INV006\",\n                \"Pending\",\n                \"Bank Transfer\",\n                \"$200.00\",\n                \"2024-04-01\",\n            ),\n            (\"INV007\", \"Unpaid\", \"Credit Card\", \"$300.00\", \"2024-04-15\"),\n        ];\n\n        v_flex()\n            .size_full()\n            .gap_6()\n            .child(\n                h_flex().gap_3().child(\n                    ButtonGroup::new(\"toggle-size\")\n                        .outline()\n                        .compact()\n                        .child(\n                            Button::new(\"xsmall\")\n                                .label(\"XSmall\")\n                                .selected(self.size == Size::XSmall),\n                        )\n                        .child(\n                            Button::new(\"small\")\n                                .label(\"Small\")\n                                .selected(self.size == Size::Small),\n                        )\n                        .child(\n                            Button::new(\"medium\")\n                                .label(\"Medium\")\n                                .selected(self.size == Size::Medium),\n                        )\n                        .child(\n                            Button::new(\"large\")\n                                .label(\"Large\")\n                                .selected(self.size == Size::Large),\n                        )\n                        .on_click(cx.listener(|this, selecteds: &Vec<usize>, window, cx| {\n                            let size = match selecteds[0] {\n                                0 => Size::XSmall,\n                                1 => Size::Small,\n                                2 => Size::Medium,\n                                3 => Size::Large,\n                                _ => unreachable!(),\n                            };\n                            this.set_size(size, window, cx);\n                        })),\n                ),\n            )\n            .child(\n                section(\"Table\").child(\n                    Table::new()\n                        .with_size(self.size)\n                        .child(\n                            TableHeader::new().child(\n                                TableRow::new()\n                                    .child(TableHead::new().w(px(150.)).child(\"Invoice\"))\n                                    .child(TableHead::new().col_span(2).child(\"Status\"))\n                                    .child(TableHead::new().text_right().child(\"Amount\"))\n                                    .child(TableHead::new().text_right().child(\"Date\")),\n                            ),\n                        )\n                        .child(TableBody::new().children(invoices.iter().map(\n                            |(invoice, status, method, amount, date)| {\n                                TableRow::new()\n                                    .child(TableCell::new().w(px(150.)).child(invoice.to_string()))\n                                    .child(TableCell::new().child(status_tag(status)))\n                                    .child(TableCell::new().child(method.to_string()))\n                                    .child(TableCell::new().text_right().child(amount.to_string()))\n                                    .child(TableCell::new().text_right().child(date.to_string()))\n                            },\n                        )))\n                        .child(\n                            TableFooter::new().child(\n                                TableRow::new()\n                                    .child(TableCell::new().col_span(3).child(\"Total\"))\n                                    .child(\n                                        TableCell::new()\n                                            .col_span(2)\n                                            .text_right()\n                                            .child(\"$2,250.00\"),\n                                    ),\n                            ),\n                        )\n                        .child(TableCaption::new().child(\"A list of your recent invoices.\")),\n                ),\n            )\n            .child(\n                section(\"With Border\").child(\n                    Table::new()\n                        .with_size(self.size)\n                        .border_1()\n                        .border_color(cx.theme().border)\n                        .rounded(cx.theme().radius)\n                        .child(\n                            TableHeader::new().child(\n                                TableRow::new()\n                                    .child(TableHead::new().w(px(100.)).child(\"Invoice\"))\n                                    .child(TableHead::new().child(\"Method\"))\n                                    .child(TableHead::new().text_right().child(\"Amount\"))\n                                    .child(TableHead::new().text_right().child(\"Date\")),\n                            ),\n                        )\n                        .child(\n                            TableBody::new().children(invoices.iter().enumerate().take(6).map(\n                                |(ix, (invoice, _, method, amount, date))| {\n                                    TableRow::new()\n                                        .when(ix % 2 != 0, |this| this.bg(cx.theme().table_even))\n                                        .child(\n                                            TableCell::new().w(px(100.)).child(invoice.to_string()),\n                                        )\n                                        .child(TableCell::new().child(method.to_string()))\n                                        .child(\n                                            TableCell::new().text_right().child(amount.to_string()),\n                                        )\n                                        .child(\n                                            TableCell::new().text_right().child(date.to_string()),\n                                        )\n                                },\n                            )),\n                        ),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/tabs_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render,\n    Styled, Window,\n};\n\nuse gpui_component::{\n    ActiveTheme as _, IconName, Selectable as _, Sizable, Size,\n    button::{Button, ButtonGroup, ButtonVariants},\n    checkbox::Checkbox,\n    h_flex,\n    tab::{Tab, TabBar},\n    v_flex,\n};\n\nuse crate::section;\n\npub struct TabsStory {\n    focus_handle: FocusHandle,\n    active_tab_ix: usize,\n    size: Size,\n    menu: bool,\n}\n\nimpl super::Story for TabsStory {\n    fn title() -> &'static str {\n        \"Tabs\"\n    }\n\n    fn description() -> &'static str {\n        \"A set of layered sections of content—known as tab panels—that are displayed one at a time.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl TabsStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            active_tab_ix: 0,\n            size: Size::default(),\n            menu: false,\n        }\n    }\n\n    fn set_active_tab(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Self>) {\n        self.active_tab_ix = ix;\n        cx.notify();\n    }\n\n    fn set_size(&mut self, size: Size, _: &mut Window, cx: &mut Context<Self>) {\n        self.size = size;\n        cx.notify();\n    }\n}\n\nimpl Focusable for TabsStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for TabsStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .w_full()\n            .gap_3()\n            .child(\n                h_flex()\n                    .gap_3()\n                    .child(\n                        ButtonGroup::new(\"toggle-size\")\n                            .outline()\n                            .compact()\n                            .child(\n                                Button::new(\"xsmall\")\n                                    .label(\"XSmall\")\n                                    .selected(self.size == Size::XSmall),\n                            )\n                            .child(\n                                Button::new(\"small\")\n                                    .label(\"Small\")\n                                    .selected(self.size == Size::Small),\n                            )\n                            .child(\n                                Button::new(\"medium\")\n                                    .label(\"Medium\")\n                                    .selected(self.size == Size::Medium),\n                            )\n                            .child(\n                                Button::new(\"large\")\n                                    .label(\"Large\")\n                                    .selected(self.size == Size::Large),\n                            )\n                            .on_click(cx.listener(|this, selecteds: &Vec<usize>, window, cx| {\n                                let size = match selecteds[0] {\n                                    0 => Size::XSmall,\n                                    1 => Size::Small,\n                                    2 => Size::Medium,\n                                    3 => Size::Large,\n                                    _ => unreachable!(),\n                                };\n                                this.set_size(size, window, cx);\n                            })),\n                    )\n                    .child(\n                        Checkbox::new(\"show-menu\")\n                            .label(\"More menu\")\n                            .checked(self.menu)\n                            .on_click(cx.listener(|this, _, _, cx| {\n                                this.menu = !this.menu;\n                                cx.notify();\n                            })),\n                    ),\n            )\n            .child(\n                section(\"Tabs\").max_w_md().child(\n                    TabBar::new(\"tabs\")\n                        .w_full()\n                        .with_size(self.size)\n                        .menu(self.menu)\n                        .selected_index(self.active_tab_ix)\n                        .on_click(cx.listener(|this, ix: &usize, window, cx| {\n                            this.set_active_tab(*ix, window, cx);\n                        }))\n                        .border_t_1()\n                        .border_color(cx.theme().border)\n                        .prefix(\n                            h_flex()\n                                .mx_1()\n                                .child(\n                                    Button::new(\"back\")\n                                        .ghost()\n                                        .xsmall()\n                                        .icon(IconName::ArrowLeft),\n                                )\n                                .child(\n                                    Button::new(\"forward\")\n                                        .ghost()\n                                        .xsmall()\n                                        .icon(IconName::ArrowRight),\n                                ),\n                        )\n                        .child(Tab::new().label(\"Account\"))\n                        .child(Tab::new().label(\"Profile\").disabled(true))\n                        .child(Tab::new().label(\"Documents\"))\n                        .child(Tab::new().label(\"Mail\"))\n                        .child(Tab::new().label(\"Appearance\"))\n                        .child(Tab::new().label(\"Settings\"))\n                        .child(Tab::new().label(\"About\"))\n                        .child(Tab::new().label(\"License\"))\n                        .suffix(\n                            h_flex()\n                                .mx_1()\n                                .child(Button::new(\"inbox\").ghost().xsmall().icon(IconName::Inbox))\n                                .child(\n                                    Button::new(\"more\")\n                                        .ghost()\n                                        .xsmall()\n                                        .icon(IconName::Ellipsis),\n                                ),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Underline Tabs\").max_w_md().child(\n                    TabBar::new(\"underline\")\n                        .w_full()\n                        .underline()\n                        .with_size(self.size)\n                        .menu(self.menu)\n                        .selected_index(self.active_tab_ix)\n                        .on_click(cx.listener(|this, ix: &usize, window, cx| {\n                            this.set_active_tab(*ix, window, cx);\n                        }))\n                        .child(\"Account\")\n                        .child(\"Profile\")\n                        .child(\"Documents\")\n                        .child(\"Mail\")\n                        .child(\"Appearance\")\n                        .child(\"Settings\")\n                        .child(\"About\")\n                        .child(\"License\"),\n                ),\n            )\n            .child(\n                section(\"Pill Tabs\").max_w_md().child(\n                    TabBar::new(\"pill\")\n                        .w_full()\n                        .pill()\n                        .with_size(self.size)\n                        .menu(self.menu)\n                        .selected_index(self.active_tab_ix)\n                        .on_click(cx.listener(|this, ix: &usize, window, cx| {\n                            this.set_active_tab(*ix, window, cx);\n                        }))\n                        .child(Tab::new().label(\"Account\"))\n                        .child(Tab::new().label(\"Profile\").disabled(true))\n                        .child(Tab::new().label(\"Documents & Files\"))\n                        .child(Tab::new().label(\"Mail\"))\n                        .child(Tab::new().label(\"Appearance\"))\n                        .child(Tab::new().label(\"Settings\"))\n                        .child(Tab::new().label(\"About\"))\n                        .child(Tab::new().label(\"License\")),\n                ),\n            )\n            .child(\n                section(\"Outline Tabs\").max_w_md().child(\n                    TabBar::new(\"outline\")\n                        .w_full()\n                        .outline()\n                        .with_size(self.size)\n                        .menu(self.menu)\n                        .selected_index(self.active_tab_ix)\n                        .on_click(cx.listener(|this, ix: &usize, window, cx| {\n                            this.set_active_tab(*ix, window, cx);\n                        }))\n                        .child(Tab::new().label(\"Account\"))\n                        .child(Tab::new().label(\"Profile\").disabled(true))\n                        .child(Tab::new().label(\"Documents & Files\"))\n                        .child(Tab::new().label(\"Mail\"))\n                        .child(Tab::new().label(\"Appearance\"))\n                        .child(Tab::new().label(\"Settings\"))\n                        .child(Tab::new().label(\"About\"))\n                        .child(Tab::new().label(\"License\")),\n                ),\n            )\n            .child(\n                section(\"Segmented Tabs\").max_w_md().child(\n                    TabBar::new(\"segmented\")\n                        .w_full()\n                        .segmented()\n                        .with_size(self.size)\n                        .menu(self.menu)\n                        .selected_index(self.active_tab_ix)\n                        .on_click(cx.listener(|this, ix: &usize, window, cx| {\n                            this.set_active_tab(*ix, window, cx);\n                        }))\n                        .child(IconName::Bot)\n                        .child(IconName::Calendar)\n                        .child(IconName::Map)\n                        .children(vec![\"Appearance\", \"Settings\", \"About\", \"License\"]),\n                ),\n            )\n            .child(\n                section(\"Segmented Tabs (With filling space)\")\n                    .max_w_md()\n                    .child(\n                        TabBar::new(\"flex tabs\")\n                            .w_full()\n                            .segmented()\n                            .with_size(self.size)\n                            .selected_index(self.active_tab_ix)\n                            .on_click(cx.listener(|this, ix: &usize, window, cx| {\n                                this.set_active_tab(*ix, window, cx);\n                            }))\n                            .child(Tab::new().flex_1().label(\"About\"))\n                            .child(Tab::new().flex_1().label(\"Profile\")),\n                    ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/tag_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render,\n    Styled, Window, px,\n};\n\nuse gpui_component::{ColorName, Sizable, h_flex, indigo_50, indigo_500, tag::Tag, v_flex};\n\nuse crate::section;\n\npub struct TagStory {\n    focus_handle: FocusHandle,\n}\n\nimpl super::Story for TagStory {\n    fn title() -> &'static str {\n        \"Tag\"\n    }\n\n    fn description() -> &'static str {\n        \"A short item that can be used to categorize or label content.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl TagStory {\n    pub(crate) fn new(_: &mut Window, cx: &mut App) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n}\nimpl Focusable for TagStory {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\nimpl Render for TagStory {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .w_full()\n            .gap_3()\n            .child(\n                section(\"Tag (default)\").child(\n                    h_flex()\n                        .gap_2()\n                        .child(Tag::primary().child(\"Tag\"))\n                        .child(Tag::secondary().child(\"Secondary\"))\n                        .child(Tag::danger().child(\"Danger\"))\n                        .child(Tag::success().child(\"Success\"))\n                        .child(Tag::warning().child(\"Warning\"))\n                        .child(Tag::info().child(\"Info\"))\n                        .child(\n                            Tag::custom(indigo_500(), indigo_50(), indigo_500()).child(\"Custom\"),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Tag (outline)\").child(\n                    h_flex()\n                        .gap_2()\n                        .child(Tag::primary().outline().child(\"Tag\"))\n                        .child(Tag::secondary().outline().child(\"Secondary\"))\n                        .child(Tag::danger().outline().child(\"Danger\"))\n                        .child(Tag::success().outline().child(\"Success\"))\n                        .child(Tag::warning().outline().child(\"Warning\"))\n                        .child(Tag::info().outline().child(\"Info\"))\n                        .child(\n                            Tag::custom(indigo_500(), indigo_500(), indigo_500())\n                                .outline()\n                                .child(\"Custom\"),\n                        ),\n                ),\n            )\n            .child(\n                section(\"Tag (small)\").child(\n                    h_flex()\n                        .gap_2()\n                        .child(Tag::primary().small().child(\"Tag\"))\n                        .child(Tag::secondary().small().child(\"Secondary\"))\n                        .child(Tag::danger().small().child(\"Danger\"))\n                        .child(Tag::success().small().child(\"Success\"))\n                        .child(Tag::warning().small().child(\"Warning\"))\n                        .child(Tag::info().small().child(\"Info\")),\n                ),\n            )\n            .child(\n                section(\"Tag (rounded full)\").child(\n                    h_flex()\n                        .gap_2()\n                        .child(Tag::primary().rounded_full().child(\"Tag\"))\n                        .child(Tag::secondary().rounded_full().child(\"Secondary\"))\n                        .child(Tag::danger().rounded_full().child(\"Danger\"))\n                        .child(Tag::success().rounded_full().child(\"Success\"))\n                        .child(Tag::warning().rounded_full().child(\"Warning\"))\n                        .child(Tag::info().rounded_full().child(\"Info\")),\n                ),\n            )\n            .child(\n                section(\"Tag (small with rounded full)\").child(\n                    h_flex()\n                        .gap_2()\n                        .child(Tag::primary().small().rounded_full().child(\"Tag\"))\n                        .child(Tag::secondary().small().rounded_full().child(\"Secondary\"))\n                        .child(Tag::danger().small().rounded_full().child(\"Danger\"))\n                        .child(Tag::success().small().rounded_full().child(\"Success\"))\n                        .child(Tag::warning().small().rounded_full().child(\"Warning\"))\n                        .child(Tag::info().small().rounded_full().child(\"Info\")),\n                ),\n            )\n            .child(\n                section(\"Tag (rounded 0px)\").child(\n                    h_flex()\n                        .gap_2()\n                        .child(Tag::primary().small().rounded(px(0.)).child(\"Tag\"))\n                        .child(Tag::secondary().small().rounded(px(0.)).child(\"Secondary\"))\n                        .child(Tag::danger().small().rounded(px(0.)).child(\"Danger\"))\n                        .child(Tag::success().small().rounded(px(0.)).child(\"Success\"))\n                        .child(Tag::warning().small().rounded(px(0.)).child(\"Warning\"))\n                        .child(Tag::info().small().rounded(px(0.)).child(\"Info\")),\n                ),\n            )\n            .child(\n                section(\"Color Tags\").child(\n                    v_flex().gap_4().child(\n                        h_flex().gap_2().flex_wrap().children(\n                            ColorName::all()\n                                .into_iter()\n                                .filter(|color| *color != ColorName::Gray)\n                                .map(|color| Tag::color(color).child(color.to_string())),\n                        ),\n                    ),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/textarea_story.rs",
    "content": "use gpui::{\n    App, AppContext as _, ClickEvent, Context, Entity, Focusable, IntoElement, ParentElement as _,\n    Render, Styled, Window, px,\n};\n\nuse crate::section;\nuse gpui_component::{\n    Sizable,\n    button::Button,\n    h_flex,\n    input::{Input, InputState},\n    v_flex,\n};\n\npub fn init(_: &mut App) {}\n\npub struct TextareaStory {\n    textarea: Entity<InputState>,\n    textarea_auto_grow: Entity<InputState>,\n    textarea_no_wrap: Entity<InputState>,\n    textarea_auto_grow_no_wrap: Entity<InputState>,\n}\n\nimpl super::Story for TextareaStory {\n    fn title() -> &'static str {\n        \"Textarea\"\n    }\n\n    fn description() -> &'static str {\n        \"Input with multi-line mode.\"\n    }\n\n    fn closable() -> bool {\n        false\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl TextareaStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let textarea = cx.new(|cx| {\n            InputState::new(window, cx)\n                .multi_line(true)\n                .rows(10)\n                .placeholder(\"Enter text here...\")\n                .searchable(true)\n                .default_value(\n                    unindent::unindent(\n                        r#\"Hello 世界，this is GPUI component.\n\n                    The GPUI Component is a collection of UI components for GPUI framework, including.\n\n                    Button, Input, Checkbox, Radio, Dropdown, Tab, and more...\n\n                    Here is an application that is built by using GPUI Component.\n\n                    > This application is still under development, not published yet.\n\n                    ![image](https://github.com/user-attachments/assets/559a648d-19df-4b5a-b563-b78cc79c8894)\n\n                    ![image](https://github.com/user-attachments/assets/5e06ad5d-7ea0-43db-8d13-86a240da4c8d)\n\n                    ## Demo\n\n                    If you want to see the demo, here is a some demo applications.\n                    \"#,\n                    )\n                )\n        });\n\n        let textarea_no_wrap = cx.new(|cx| {\n            InputState::new(window, cx)\n                .multi_line(true)\n                .rows(6)\n                .soft_wrap(false)\n                .default_value(\"This is a very long line of text to test if the horizontal scrolling function is working properly, and it should not wrap automatically but display a horizontal scrollbar.\\nThe second line is also very long text, used to test the horizontal scrolling effect under multiple lines, and you can input more content to test.\\nThe third line: Here you can input other long text content that requires horizontal scrolling.\\n\")\n        });\n\n        let textarea_auto_grow = cx.new(|cx| {\n            InputState::new(window, cx)\n                .auto_grow(1, 5)\n                .placeholder(\"Enter text here...\")\n                .default_value(\n                    \"Hello 世界 this is a very long line of text \\\n                    to test if the horizontal scrolling function is working \\\n                    properly, and it should not wrap automatically but display \\\n                    a horizontal scrollbar.\\n\\\n                    The second line is also very long text, used to test the \\\n                    horizontal scrolling effect under multiple lines, and you \\\n                    can input more content to test.\\nThe third line: Here you \\\n                    can input other long text content that requires \\\n                    horizontal scrolling.\\n\",\n                )\n        });\n\n        let textarea_auto_grow_no_wrap = cx.new(|cx| {\n            InputState::new(window, cx)\n                .auto_grow(1, 5)\n                .soft_wrap(false)\n                .placeholder(\"Enter text here...\")\n                .default_value(\"Hello 世界，this is GPUI component.\")\n        });\n\n        Self {\n            textarea,\n            textarea_auto_grow,\n            textarea_no_wrap,\n            textarea_auto_grow_no_wrap,\n        }\n    }\n\n    fn on_insert_text_to_textarea(\n        &mut self,\n        _: &ClickEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.textarea.update(cx, |input, cx| {\n            input.insert(\"Hello 你好\", window, cx);\n        });\n    }\n\n    fn on_replace_text_to_textarea(\n        &mut self,\n        _: &ClickEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.textarea.update(cx, |input, cx| {\n            input.replace(\"Hello 你好\", window, cx);\n        });\n    }\n}\n\nimpl Focusable for TextareaStory {\n    fn focus_handle(&self, cx: &gpui::App) -> gpui::FocusHandle {\n        self.textarea.focus_handle(cx)\n    }\n}\n\nimpl Render for TextareaStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let loc = self.textarea.read(cx).cursor_position();\n\n        v_flex()\n            .w_full()\n            .gap_3()\n            .child(\n                section(\"Textarea\").child(\n                    v_flex()\n                        .gap_2()\n                        .w_full()\n                        .child(Input::new(&self.textarea).h(px(320.)))\n                        .child(\n                            h_flex()\n                                .justify_between()\n                                .child(\n                                    h_flex()\n                                        .gap_2()\n                                        .child(\n                                            Button::new(\"btn-insert-text\")\n                                                .outline()\n                                                .xsmall()\n                                                .label(\"Insert Text\")\n                                                .on_click(\n                                                    cx.listener(Self::on_insert_text_to_textarea),\n                                                ),\n                                        )\n                                        .child(\n                                            Button::new(\"btn-replace-text\")\n                                                .outline()\n                                                .xsmall()\n                                                .label(\"Replace Text\")\n                                                .on_click(\n                                                    cx.listener(Self::on_replace_text_to_textarea),\n                                                ),\n                                        ),\n                                )\n                                .child(format!(\"{}:{}\", loc.line, loc.character)),\n                        ),\n                ),\n            )\n            .child(\n                section(\"No Wrap\")\n                    .max_w_md()\n                    .child(Input::new(&self.textarea_no_wrap).h(px(200.))),\n            )\n            .child(\n                section(\"Auto Grow\")\n                    .max_w_md()\n                    .child(Input::new(&self.textarea_auto_grow)),\n            )\n            .child(\n                section(\"Auto Grow with No Wrap\")\n                    .max_w_md()\n                    .child(Input::new(&self.textarea_auto_grow_no_wrap)),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/theme_story/checkerboard.rs",
    "content": "use gpui::*;\nuse gpui_component::ActiveTheme as _;\n\n#[derive(IntoElement)]\npub struct Checkerboard {\n    children: Vec<AnyElement>,\n    is_dark: bool,\n}\n\nimpl Checkerboard {\n    pub fn new(is_dark: bool) -> Self {\n        Self {\n            children: Vec::new(),\n            is_dark,\n        }\n    }\n}\n\nimpl ParentElement for Checkerboard {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl RenderOnce for Checkerboard {\n    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let square_size = px(12.);\n        // Use a subtle difference for the checkerboard\n        let (c1, c2) = if self.is_dark {\n            // Dark mode: dark grey and slightly lighter grey\n            (hsla(0., 0., 0.1, 1.), hsla(0., 0., 0.13, 1.))\n        } else {\n            // Light mode: white and light grey\n            (hsla(0., 0., 1.0, 1.), hsla(0., 0., 0.95, 1.))\n        };\n\n        div()\n            .bg(c1)\n            .rounded(cx.theme().radius_lg)\n            .overflow_hidden()\n            .size_full()\n            .child(\n                gpui::canvas(\n                    move |_, _, _| (),\n                    move |bounds, _, window, _| {\n                        let size = square_size;\n                        let rows = (bounds.size.height / size).ceil() as i32;\n                        let cols = (bounds.size.width / size).ceil() as i32;\n\n                        for row in 0..rows {\n                            for col in 0..cols {\n                                if (row + col) % 2 == 0 {\n                                    let origin = bounds.origin\n                                        + gpui::point(size * (col as f32), size * (row as f32));\n\n                                    window.paint_quad(gpui::PaintQuad {\n                                        bounds: gpui::Bounds {\n                                            origin,\n                                            size: gpui::size(size, size),\n                                        },\n                                        corner_radii: gpui::Corners::default(),\n                                        background: c2.into(),\n                                        border_widths: gpui::Edges::default(),\n                                        border_color: gpui::transparent_black(),\n                                        border_style: gpui::BorderStyle::default(),\n                                    });\n                                }\n                            }\n                        }\n                    },\n                )\n                .absolute()\n                .size_full(),\n            )\n            .children(self.children)\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/theme_story/color_theme_story.rs",
    "content": "use gpui::{prelude::FluentBuilder, *};\nuse gpui_component::{\n    ActiveTheme as _, Icon, IconName, IndexPath, StyledExt as _, ThemeColor,\n    button::{Button, ButtonVariants as _},\n    h_flex,\n    input::{Input, InputEvent, InputState},\n    menu::PopupMenuItem,\n    scroll::ScrollableElement,\n    select::{Select, SelectEvent, SelectItem, SelectState},\n    sidebar::{Sidebar, SidebarMenu, SidebarMenuItem},\n    switch::Switch,\n    v_flex,\n};\n\nuse crate::stories::theme_story::checkerboard::Checkerboard;\n\nuse std::collections::BTreeMap;\nuse std::rc::Rc;\n\n#[derive(Clone)]\nstruct ColorEntry {\n    name: String,\n    color: Hsla,\n    hex: String,\n    is_explicit: bool,\n}\n\n#[derive(Clone)]\nstruct ColorCategory {\n    name: String,\n    entries: Vec<ColorEntry>,\n}\n\n#[derive(Clone, PartialEq)]\nstruct ThemeItem {\n    name: SharedString,\n    is_active: bool,\n}\n\nimpl ThemeItem {\n    fn new(name: impl Into<SharedString>, is_active: bool) -> Self {\n        Self {\n            name: name.into(),\n            is_active,\n        }\n    }\n}\n\nimpl SelectItem for ThemeItem {\n    type Value = SharedString;\n\n    fn title(&self) -> SharedString {\n        self.name.clone()\n    }\n\n    fn value(&self) -> &Self::Value {\n        &self.name\n    }\n\n    fn render(&self, _window: &mut Window, cx: &mut App) -> impl IntoElement {\n        h_flex()\n            .w_full()\n            .items_center()\n            .gap_2()\n            .child(\n                div()\n                    .size(rems(1.0))\n                    .flex_shrink_0()\n                    .when(self.is_active, |this| {\n                        this.child(\n                            Icon::new(IconName::Check)\n                                .size(rems(1.0))\n                                .text_color(cx.theme().primary),\n                        )\n                    }),\n            )\n            .child(self.name.clone())\n    }\n}\n\npub struct ThemeColorsStory {\n    select_state: Entity<SelectState<Vec<ThemeItem>>>,\n    selected_theme_name: SharedString,\n    show_all_colors: bool,\n    sidebar_render_key: usize,\n    force_open_state: Option<bool>,\n    filter_by_value: Option<Hsla>,\n    filter_input: Entity<InputState>,\n    all_categories: Vec<ColorCategory>,\n    categories: Vec<ColorCategory>,\n}\n\nimpl crate::stories::Story for ThemeColorsStory {\n    fn title() -> &'static str {\n        \"Theme Colors\"\n    }\n\n    fn description() -> &'static str {\n        \"A color theme viewer to explore colors organized by categories.\"\n        // Themes are loaded by applying user-defined color overrides to a default base theme,\n        // with inherited colors marked by an indicator dot.\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl ThemeColorsStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        use gpui_component::ThemeRegistry;\n\n        let registry = ThemeRegistry::global(cx);\n        let mut themes = registry.sorted_themes();\n\n        themes.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));\n\n        let active_theme_name = cx.theme().theme_name().clone();\n        let items: Vec<ThemeItem> = themes\n            .iter()\n            .map(|theme| ThemeItem::new(theme.name.clone(), theme.name == active_theme_name))\n            .collect();\n\n        let current_theme = active_theme_name.clone();\n        let selected_index = items.iter().position(|item| item.name == current_theme);\n        let selected_path = selected_index.map(|idx| IndexPath::default().row(idx));\n        let select_state = cx.new(|cx| SelectState::new(items, selected_path, window, cx));\n\n        let mut this = Self {\n            select_state: select_state.clone(),\n            selected_theme_name: current_theme,\n            show_all_colors: false,\n            sidebar_render_key: 0,\n            force_open_state: None,\n            filter_by_value: None,\n            filter_input: cx.new(|cx| InputState::new(window, cx).placeholder(\"Search...\")),\n            all_categories: Vec::new(),\n            categories: Vec::new(),\n        };\n\n        cx.subscribe(\n            &select_state,\n            |this, _, event: &SelectEvent<Vec<ThemeItem>>, cx| {\n                let SelectEvent::Confirm(theme_name) = event;\n                if let Some(theme_name) = theme_name {\n                    this.selected_theme_name = theme_name.clone();\n                    this.filter_by_value = None;\n                    this.all_categories.clear();\n                    this.compute_categories(cx);\n                    cx.notify();\n                }\n            },\n        )\n        .detach();\n\n        cx.subscribe(&this.filter_input, |this, _, event, cx| {\n            if let InputEvent::Change = event {\n                this.compute_categories(cx);\n                cx.notify();\n            }\n        })\n        .detach();\n\n        this.compute_categories(cx);\n        this\n    }\n\n    fn get_theme_colors(&self, cx: &Context<Self>) -> ThemeColor {\n        use gpui_component::{Theme as UITheme, ThemeRegistry};\n\n        if let Some(theme_config) = ThemeRegistry::global(cx)\n            .themes()\n            .get(&self.selected_theme_name)\n            .cloned()\n        {\n            let mut temp_theme = if theme_config.mode.is_dark() {\n                UITheme::from(ThemeColor::dark().as_ref())\n            } else {\n                UITheme::from(ThemeColor::light().as_ref())\n            };\n\n            // Apply the config to get proper colors using the public API\n            temp_theme.apply_config(&theme_config);\n            temp_theme.colors\n        } else {\n            // Fallback to current theme if selected theme not found\n            **cx.theme()\n        }\n    }\n\n    fn get_isolated_theme(&self, cx: &App) -> (ThemeColor, bool) {\n        use gpui_component::{Theme as UITheme, ThemeRegistry};\n\n        let registry = ThemeRegistry::global(cx);\n\n        // Look up the selected theme configuration\n        let selected_theme_config = registry.themes().get(&self.selected_theme_name);\n\n        let is_dark = if let Some(config) = selected_theme_config {\n            config.mode.is_dark()\n        } else {\n            // Fallback to system appearance if selected theme lookup fails\n            let appearance = cx.window_appearance();\n            appearance == WindowAppearance::Dark || appearance == WindowAppearance::VibrantDark\n        };\n\n        let theme_config = if is_dark {\n            registry.default_dark_theme()\n        } else {\n            registry.default_light_theme()\n        };\n\n        let mut temp_theme = if theme_config.mode.is_dark() {\n            UITheme::from(ThemeColor::dark().as_ref())\n        } else {\n            UITheme::from(ThemeColor::light().as_ref())\n        };\n\n        temp_theme.apply_config(theme_config);\n        (temp_theme.colors, is_dark)\n    }\n\n    fn compute_categories(&mut self, cx: &Context<Self>) {\n        use gpui_component::ThemeRegistry;\n\n        if self.all_categories.is_empty() {\n            let theme = self.get_theme_colors(cx);\n            let registry = ThemeRegistry::global(cx);\n            let theme_config = registry.themes().get(&self.selected_theme_name).cloned();\n\n            self.all_categories = format_colors(&theme, theme_config.as_ref().map(|c| &c.colors));\n        }\n\n        let mut categories = self.all_categories.clone();\n\n        if let Some(filter_value) = self.filter_by_value {\n            categories = filter_categories(categories, |entry| {\n                colors_equal_u8(entry.color, filter_value)\n            });\n        } else if !self.show_all_colors {\n            categories = filter_categories(categories, |entry| entry.is_explicit);\n        }\n\n        let query = self.filter_input.read(cx).value().trim().to_lowercase();\n        if !query.is_empty() {\n            let normalized_query = query.strip_prefix('#').unwrap_or(&query);\n            categories = categories\n                .into_iter()\n                .filter_map(\n                    |ColorCategory {\n                         name: category,\n                         entries: colors,\n                     }| {\n                        let category_matches = category.to_lowercase().contains(&query);\n                        let filtered_colors: Vec<_> = colors\n                            .into_iter()\n                            .filter(|entry| {\n                                if category_matches || entry.name.to_lowercase().contains(&query) {\n                                    return true;\n                                }\n\n                                // Hex matching\n                                entry.hex.starts_with(normalized_query)\n                            })\n                            .collect();\n\n                        if filtered_colors.is_empty() {\n                            None\n                        } else {\n                            Some(ColorCategory {\n                                name: category,\n                                entries: filtered_colors,\n                            })\n                        }\n                    },\n                )\n                .collect();\n        }\n\n        self.categories = categories;\n    }\n\n    fn render_color_swatch(\n        name: String,\n        color: Hsla,\n        hex: String,\n        is_explicit: bool,\n        isolated_theme: &ThemeColor,\n        cx: &App,\n    ) -> impl IntoElement {\n        use gpui_component::{WindowExt as _, clipboard::Clipboard};\n\n        let rgb_str = format!(\"#{}\", hex);\n        let swatch_group = format!(\"swatch-{}\", name);\n\n        h_flex()\n            .group(swatch_group.clone())\n            .gap_3()\n            .items_center()\n            .child(\n                div()\n                    .size_16()\n                    .rounded(cx.theme().radius)\n                    .bg(color)\n                    .border_1()\n                    .border_color(isolated_theme.border)\n                    .flex_shrink_0(),\n            )\n            .child(\n                v_flex()\n                    .gap_1()\n                    .flex_1()\n                    .child(\n                        h_flex()\n                            .gap_2()\n                            .items_center()\n                            .when(!is_explicit, |this| {\n                                this.child(\n                                    div()\n                                        .size_1p5()\n                                        .rounded_full()\n                                        .bg(isolated_theme.foreground)\n                                        .flex_shrink_0(),\n                                )\n                            })\n                            .child(\n                                div()\n                                    .text_sm()\n                                    .font_medium()\n                                    .when(!is_explicit, |this: Div| {\n                                        this.text_color(isolated_theme.muted_foreground)\n                                    })\n                                    .when(is_explicit, |this| {\n                                        this.text_color(isolated_theme.foreground)\n                                    })\n                                    .child(name.clone()),\n                            ),\n                    )\n                    .child(\n                        h_flex()\n                            .gap_1()\n                            .items_center()\n                            .child(\n                                div()\n                                    .text_sm()\n                                    .text_color(isolated_theme.muted_foreground)\n                                    .child(rgb_str.clone()),\n                            )\n                            .child(\n                                div()\n                                    .invisible()\n                                    .group_hover(swatch_group, |this| this.visible())\n                                    .child(\n                                        Clipboard::new(format!(\"copy-{}\", name))\n                                            .value(rgb_str)\n                                            .on_copied(move |value, window, cx| {\n                                                window.push_notification(\n                                                    format!(\"Copied {} to clipboard\", value),\n                                                    cx,\n                                                )\n                                            }),\n                                    ),\n                            ),\n                    ),\n            )\n    }\n\n    fn render_left_panel(&self, _: &mut Window, cx: &mut Context<Self>) -> Sidebar<SidebarMenu> {\n        let categories = &self.categories;\n        let is_filtering = self.filter_by_value.is_some();\n        let entity_ref = cx.entity().clone();\n\n        let expand_all = Rc::new(cx.listener(\n            |this: &mut Self, _: &ClickEvent, _: &mut Window, cx: &mut Context<Self>| {\n                this.sidebar_render_key += 1;\n                this.force_open_state = Some(true);\n                cx.notify();\n            },\n        ));\n\n        let collapse_all = Rc::new(cx.listener(\n            |this: &mut Self, _: &ClickEvent, _: &mut Window, cx: &mut Context<Self>| {\n                this.sidebar_render_key += 1;\n                this.force_open_state = Some(false);\n                cx.notify();\n            },\n        ));\n\n        Sidebar::new(format!(\"color-theme-sidebar-{}\", self.sidebar_render_key))\n            .w(px(300.))\n            .border_0()\n            .header(Input::new(&self.filter_input).prefix(IconName::Search))\n            .child(\n                SidebarMenu::new().children(categories.iter().enumerate().map(\n                    |(\n                        idx,\n                        ColorCategory {\n                            name: category_name,\n                            entries: colors,\n                        },\n                    )| {\n                        let is_open = self.force_open_state.unwrap_or_else(|| idx == 0);\n\n                        SidebarMenuItem::new(category_name.clone())\n                            .default_open(is_open)\n                            .click_to_open(true)\n                            .context_menu({\n                                let expand_all = expand_all.clone();\n                                let collapse_all = collapse_all.clone();\n                                move |menu, _, _| {\n                                    menu.item(PopupMenuItem::new(\"Expand All\").on_click({\n                                        let expand_all = expand_all.clone();\n                                        move |ev, window, cx| (expand_all)(ev, window, cx)\n                                    }))\n                                    .item(\n                                        PopupMenuItem::new(\"Collapse All\").on_click({\n                                            let collapse_all = collapse_all.clone();\n                                            move |ev, window, cx| (collapse_all)(ev, window, cx)\n                                        }),\n                                    )\n                                }\n                            })\n                            .children(colors.iter().map(|entry| {\n                                let color_value = entry.color;\n                                let is_explicit = entry.is_explicit;\n                                let color_view = entity_ref.clone();\n                                SidebarMenuItem::new(entry.name.clone())\n                                    .suffix(move |_, cx| {\n                                        h_flex()\n                                            .gap_2()\n                                            .items_center()\n                                            .when(!is_explicit, |this| {\n                                                this.child(\n                                                    div()\n                                                        .size_1p5()\n                                                        .rounded_full()\n                                                        .bg(cx.theme().foreground),\n                                                )\n                                            })\n                                            .child(\n                                                div()\n                                                    .size_4()\n                                                    .rounded(cx.theme().radius.half())\n                                                    .bg(color_value)\n                                                    .border_1()\n                                                    .border_color(cx.theme().border)\n                                                    .flex_shrink_0(),\n                                            )\n                                    })\n                                    .context_menu(move |menu, _, _| {\n                                        let menu_view = color_view.clone();\n                                        if is_filtering {\n                                            menu.item(\n                                                PopupMenuItem::new(\"Show All Values\").on_click(\n                                                    move |_, _, cx| {\n                                                        menu_view.update(cx, |this, cx| {\n                                                            this.filter_by_value = None;\n                                                            this.compute_categories(cx);\n                                                            cx.notify();\n                                                        })\n                                                    },\n                                                ),\n                                            )\n                                        } else {\n                                            menu.item(\n                                                PopupMenuItem::new(\"Filter By Value\").on_click(\n                                                    move |_, _, cx| {\n                                                        menu_view.update(cx, |this, cx| {\n                                                            this.filter_by_value =\n                                                                Some(color_value);\n                                                            this.compute_categories(cx);\n                                                            cx.notify();\n                                                        })\n                                                    },\n                                                ),\n                                            )\n                                        }\n                                    })\n                            }))\n                    },\n                )),\n            )\n    }\n\n    fn render_right_panel(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let (isolated_theme, is_dark) = self.get_isolated_theme(cx);\n\n        let categories = self.categories.clone();\n        let categories_count = categories.len();\n        let list_state = window\n            .use_keyed_state(\"color-theme-right-panel-list-state\", cx, |_, _| {\n                ListState::new(19, ListAlignment::Top, px(1000.))\n            })\n            .read(cx)\n            .clone();\n        if list_state.item_count() != categories_count {\n            list_state.reset(categories_count);\n        }\n\n        div()\n            .border_1()\n            .border_color(isolated_theme.border)\n            .rounded(cx.theme().radius_lg)\n            .size_full()\n            .overflow_hidden()\n            .child(\n                Checkerboard::new(is_dark).child(\n                    v_flex()\n                        .size_full()\n                        .overflow_hidden()\n                        .rounded(cx.theme().radius_lg)\n                        .px_4()\n                        .child(\n                            list(list_state.clone(), {\n                                move |ix, _, cx| {\n                                    let ColorCategory {\n                                        name: category_name,\n                                        entries: colors,\n                                    } = categories[ix].clone();\n                                    let is_last = categories_count > 0\n                                        && ix == categories_count.saturating_sub(1);\n\n                                    v_flex()\n                                        .w_full()\n                                        .gap_3()\n                                        .pt_4()\n                                        .when(is_last, |this| this.pb_4())\n                                        .child(\n                                            div()\n                                                .text_base()\n                                                .font_semibold()\n                                                .pb_2()\n                                                .border_b_1()\n                                                .border_color(isolated_theme.border)\n                                                .text_color(isolated_theme.foreground)\n                                                .child(category_name.clone()),\n                                        )\n                                        .child(div().flex().flex_wrap().gap_4().children(\n                                            colors.iter().map(|entry| {\n                                                div().w(px(220.)).child(Self::render_color_swatch(\n                                                    entry.name.to_string(),\n                                                    entry.color,\n                                                    entry.hex.clone(),\n                                                    entry.is_explicit,\n                                                    &isolated_theme,\n                                                    cx,\n                                                ))\n                                            }),\n                                        ))\n                                        .into_any_element()\n                                }\n                            })\n                            .size_full(),\n                        )\n                        .vertical_scrollbar(&list_state),\n                ),\n            )\n    }\n}\n\nfn format_colors(\n    theme: &ThemeColor,\n    config: Option<&gpui_component::theme::ThemeConfigColors>,\n) -> Vec<ColorCategory> {\n    let json_theme = serde_json::to_value(theme).unwrap_or(serde_json::Value::Null);\n    let mut categories: BTreeMap<String, Vec<ColorEntry>> = BTreeMap::new();\n\n    // Create a set of keys present in the config (if available)\n    let config_keys: Option<std::collections::HashSet<String>> = config.map(|c| {\n        let json_config = serde_json::to_value(c).unwrap_or(serde_json::Value::Null);\n        if let serde_json::Value::Object(map) = json_config {\n            map.into_iter()\n                .filter(|(_, v)| !v.is_null())\n                .map(|(k, _)| k)\n                .collect()\n        } else {\n            std::collections::HashSet::new()\n        }\n    });\n\n    if let serde_json::Value::Object(map) = json_theme {\n        for (key, value) in map {\n            if let Ok(color) = serde_json::from_value::<Hsla>(value) {\n                let parsed = super::mapper::parse_theme_key(&key);\n                let category = parsed.category;\n                let name = parsed.name;\n\n                // Check if this key is explicit in the user config\n                let is_explicit = config_keys\n                    .as_ref()\n                    .map_or(false, |k| k.contains(&parsed.canonical_key));\n\n                categories.entry(category).or_default().push(ColorEntry {\n                    name,\n                    color,\n                    hex: hsla_to_hex(color),\n                    is_explicit,\n                });\n            }\n        }\n    }\n\n    for colors in categories.values_mut() {\n        colors.sort_by(|a, b| a.name.cmp(&b.name));\n    }\n\n    let mut categories_vec: Vec<_> = categories\n        .into_iter()\n        .map(|(name, entries)| ColorCategory { name, entries })\n        .collect();\n\n    // Custom sort: Global first, then Primary, then others\n    categories_vec.sort_by(|a, b| {\n        let priority_order = [\n            \"Global\",\n            \"Primary\",\n            \"Secondary\",\n            \"Accent\",\n            \"Base\",\n            \"Background\",\n            \"Foreground\",\n            \"Structure\",\n        ];\n\n        let a_priority = priority_order.iter().position(|&x| x == a.name.as_str());\n        let b_priority = priority_order.iter().position(|&x| x == b.name.as_str());\n\n        match (a_priority, b_priority) {\n            (Some(a_pos), Some(b_pos)) => a_pos.cmp(&b_pos),\n            (Some(_), None) => std::cmp::Ordering::Less,\n            (None, Some(_)) => std::cmp::Ordering::Greater,\n            (None, None) => a.name.cmp(&b.name),\n        }\n    });\n\n    categories_vec\n}\n\nfn hsla_to_hex(color: Hsla) -> String {\n    let rgb = color.to_rgb();\n    if color.a < 1.0 {\n        format!(\n            \"{:02x}{:02x}{:02x}{:02x}\",\n            (rgb.r * 255.0) as u8,\n            (rgb.g * 255.0) as u8,\n            (rgb.b * 255.0) as u8,\n            (color.a * 255.0) as u8\n        )\n    } else {\n        format!(\n            \"{:02x}{:02x}{:02x}\",\n            (rgb.r * 255.0) as u8,\n            (rgb.g * 255.0) as u8,\n            (rgb.b * 255.0) as u8\n        )\n    }\n}\n\n/// Compares two HSLA colors for equality at 8-bit precision.\nfn colors_equal_u8(c1: Hsla, c2: Hsla) -> bool {\n    let rgb1 = c1.to_rgb();\n    let rgb2 = c2.to_rgb();\n    let eq = |a: f32, b: f32| (a * 255.0).round() as u8 == (b * 255.0).round() as u8;\n    eq(rgb1.r, rgb2.r) && eq(rgb1.g, rgb2.g) && eq(rgb1.b, rgb2.b) && eq(c1.a, c2.a)\n}\n\n/// Filters categories by a predicate on color entries, removing empty categories.\nfn filter_categories(\n    categories: Vec<ColorCategory>,\n    predicate: impl Fn(&ColorEntry) -> bool,\n) -> Vec<ColorCategory> {\n    categories\n        .into_iter()\n        .filter_map(\n            |ColorCategory {\n                 name: category,\n                 entries: colors,\n             }| {\n                let filtered: Vec<_> = colors.into_iter().filter(|e| predicate(e)).collect();\n                if filtered.is_empty() {\n                    None\n                } else {\n                    Some(ColorCategory {\n                        name: category,\n                        entries: filtered,\n                    })\n                }\n            },\n        )\n        .collect()\n}\n\nimpl Render for ThemeColorsStory {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_4()\n            .size_full()\n            .overflow_hidden()\n            .child(\n                // Theme selector at the top\n                h_flex()\n                    .gap_x_3()\n                    .child(div().w(px(300.)).child(Select::new(&self.select_state)))\n                    .child(\n                        Button::new(\"set_theme\")\n                            .primary()\n                            .label(\"Set Theme\")\n                            .on_click(cx.listener(|this, _, window, cx| {\n                                use gpui_component::{Theme, ThemeRegistry};\n\n                                let registry = ThemeRegistry::global(cx);\n                                if let Some(theme_config) =\n                                    registry.themes().get(&this.selected_theme_name).cloned()\n                                {\n                                    let mode = theme_config.mode;\n                                    let theme = Theme::global_mut(cx);\n                                    if mode.is_dark() {\n                                        theme.dark_theme = theme_config;\n                                    } else {\n                                        theme.light_theme = theme_config;\n                                    }\n                                    Theme::change(mode, None, cx);\n                                    cx.refresh_windows();\n\n                                    // Refresh the select items to update the active checkmark\n                                    let active_theme_name = cx.theme().theme_name().clone();\n                                    let themes = ThemeRegistry::global(cx).sorted_themes();\n\n                                    // Re-create items with new active state\n                                    let mut items: Vec<ThemeItem> = themes\n                                        .iter()\n                                        .map(|theme| {\n                                            ThemeItem::new(\n                                                theme.name.clone(),\n                                                // Note: we need to handle case sensitivity if names differ,\n                                                // but usually accurate.\n                                                theme.name == active_theme_name,\n                                            )\n                                        })\n                                        .collect();\n\n                                    // Sort again to be safe/consistent\n                                    items.sort_by(|a, b| {\n                                        a.name.to_lowercase().cmp(&b.name.to_lowercase())\n                                    });\n\n                                    // Update the select state\n                                    this.select_state.update(cx, |state, cx| {\n                                        state.set_items(items, window, cx);\n                                    });\n                                }\n                            })),\n                    )\n                    .child(\n                        Switch::new(\"show_all_colors\")\n                            .checked(self.show_all_colors)\n                            .label(\"Show Inherited Colors\")\n                            .on_click(cx.listener(|this, checked: &bool, _window, cx| {\n                                this.show_all_colors = *checked;\n                                this.compute_categories(cx);\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Switch::new(\"expand_collapse_switch\")\n                            .checked(self.force_open_state == Some(true))\n                            .label(if self.force_open_state == Some(true) {\n                                \"Collapse All\"\n                            } else {\n                                \"Expand All\"\n                            })\n                            .on_click(cx.listener(|this, checked: &bool, _window, cx| {\n                                this.sidebar_render_key += 1;\n                                this.force_open_state = Some(*checked);\n                                cx.notify();\n                            })),\n                    ),\n            )\n            .child(\n                h_flex()\n                    .flex_1()\n                    .items_start()\n                    .gap_4()\n                    .child(self.render_left_panel(window, cx))\n                    .child(self.render_right_panel(window, cx)),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/theme_story/mapper.rs",
    "content": "/// A compatibility bridge for mapping theme color keys.\n///\n/// This module provides a way to translate between the legacy snake_case keys\n/// used in `ThemeColor` and the logical categories/names expected by the\n/// Color Theme Viewer.\n///\n/// ### How to eliminate this mapper\n///\n/// If the project decides to unify the theme schema project-wide (using dot-notation),\n/// follow these steps to remove this \"temporary bridge\":\n///\n/// 1. **Update `ThemeColor`**:\n///    In `crates/ui/src/theme/theme_color.rs`, add `#[serde(rename = \"...\")]` attributes\n///    to all fields of the `ThemeColor` struct to match their canonical dot-notation\n///    names (e.g., `accent_foreground` -> `#[serde(rename = \"accent.foreground\")]`).\n///\n/// 2. **Update JSON Themes**:\n///    Ensure `crates/ui/src/theme/default-theme.json` and any files in `themes/`\n///    strictly use the dot-notation keys.\n///\n/// 3. **Refactor the Viewer**:\n///    In `crates/story/src/stories/theme_story/color_theme_story.rs`, remove the call\n///    to `super::mapper::parse_theme_key` and replace it with a simple split on the '.' character.\n///\n/// 4. **Delete this file**:\n///    Remove `mapper.rs` and its module declaration in `mod.rs`.\n///\n/// Represents a parsed theme key with a category, a display name, and a canonical dot-notation key.\npub struct ParsedKey {\n    pub category: String,\n    pub name: String,\n    pub canonical_key: String,\n}\n\n/// Parses a theme key (either snake_case or dot-notation) into a logical category and name.\npub fn parse_theme_key(key: &str) -> ParsedKey {\n    // 1. Check for dot-notation (e.g., \"accent.background\")\n    if key.contains('.') {\n        let parts: Vec<&str> = key.splitn(2, '.').collect();\n        return ParsedKey {\n            category: to_title_case_full(parts[0]),\n            name: to_title_case_full(parts[1]),\n            canonical_key: key.to_string(),\n        };\n    }\n\n    // 2. Handle legacy snake_case remapping (e.g., \"accent_foreground\" -> \"Accent\" / \"Foreground\")\n    // This list attempts to reconstruct the hierarchy from the flat ThemeColor struct.\n    let (category, name, canonical) = match key {\n        // Accent\n        \"accent\" => (\"Accent\", \"Background\", \"accent.background\"),\n        \"accent_foreground\" => (\"Accent\", \"Foreground\", \"accent.foreground\"),\n\n        // Primary\n        \"primary\" => (\"Primary\", \"Background\", \"primary.background\"),\n        \"primary_active\" => (\"Primary\", \"Active Background\", \"primary.active.background\"),\n        \"primary_foreground\" => (\"Primary\", \"Foreground\", \"primary.foreground\"),\n        \"primary_hover\" => (\"Primary\", \"Hover Background\", \"primary.hover.background\"),\n\n        // Secondary\n        \"secondary\" => (\"Secondary\", \"Background\", \"secondary.background\"),\n        \"secondary_active\" => (\n            \"Secondary\",\n            \"Active Background\",\n            \"secondary.active.background\",\n        ),\n        \"secondary_foreground\" => (\"Secondary\", \"Foreground\", \"secondary.foreground\"),\n        \"secondary_hover\" => (\n            \"Secondary\",\n            \"Hover Background\",\n            \"secondary.hover.background\",\n        ),\n\n        // Sidebar\n        \"sidebar\" => (\"Sidebar\", \"Background\", \"sidebar.background\"),\n        \"sidebar_accent\" => (\"Sidebar\", \"Accent Background\", \"sidebar.accent.background\"),\n        \"sidebar_accent_foreground\" => {\n            (\"Sidebar\", \"Accent Foreground\", \"sidebar.accent.foreground\")\n        }\n        \"sidebar_border\" => (\"Sidebar\", \"Border\", \"sidebar.border\"),\n        \"sidebar_foreground\" => (\"Sidebar\", \"Foreground\", \"sidebar.foreground\"),\n        \"sidebar_primary\" => (\n            \"Sidebar\",\n            \"Primary Background\",\n            \"sidebar.primary.background\",\n        ),\n        \"sidebar_primary_foreground\" => (\n            \"Sidebar\",\n            \"Primary Foreground\",\n            \"sidebar.primary.foreground\",\n        ),\n\n        // List\n        \"list\" => (\"List\", \"Background\", \"list.background\"),\n        \"list_active\" => (\"List\", \"Active Background\", \"list.active.background\"),\n        \"list_active_border\" => (\"List\", \"Active Border\", \"list.active.border\"),\n        \"list_even\" => (\"List\", \"Even Background\", \"list.even.background\"),\n        \"list_head\" => (\"List\", \"Head Background\", \"list.head.background\"),\n        \"list_hover\" => (\"List\", \"Hover Background\", \"list.hover.background\"),\n\n        // Table\n        \"table\" => (\"Table\", \"Background\", \"table.background\"),\n        \"table_active\" => (\"Table\", \"Active Background\", \"table.active.background\"),\n        \"table_active_border\" => (\"Table\", \"Active Border\", \"table.active.border\"),\n        \"table_even\" => (\"Table\", \"Even Background\", \"table.even.background\"),\n        \"table_head\" => (\"Table\", \"Head Background\", \"table.head.background\"),\n        \"table_head_foreground\" => (\"Table\", \"Head Foreground\", \"table.head.foreground\"),\n        \"table_hover\" => (\"Table\", \"Hover Background\", \"table.hover.background\"),\n        \"table_row_border\" => (\"Table\", \"Row Border\", \"table.row.border\"),\n\n        // Tabs\n        \"tab\" => (\"Tab\", \"Background\", \"tab.background\"),\n        \"tab_active\" => (\"Tab\", \"Active Background\", \"tab.active.background\"),\n        \"tab_active_foreground\" => (\"Tab\", \"Active Foreground\", \"tab.active.foreground\"),\n        \"tab_bar\" => (\"Tab Bar\", \"Background\", \"tab_bar.background\"),\n        \"tab_bar_segmented\" => (\n            \"Tab Bar\",\n            \"Segmented Background\",\n            \"tab_bar.segmented.background\",\n        ),\n        \"tab_foreground\" => (\"Tab\", \"Foreground\", \"tab.foreground\"),\n\n        // Input\n        \"input\" => (\"Input\", \"Border\", \"input.border\"),\n        \"caret\" => (\"Input\", \"Caret\", \"caret\"),\n        \"selection\" => (\"Input\", \"Selection\", \"selection.background\"),\n\n        // Slider / Switch\n        \"slider_bar\" => (\"Slider\", \"Bar\", \"slider.background\"),\n        \"slider_thumb\" => (\"Slider\", \"Thumb\", \"slider.thumb.background\"),\n        \"switch\" => (\"Switch\", \"Background\", \"switch.background\"),\n        \"switch_thumb\" => (\"Switch\", \"Thumb\", \"switch.thumb.background\"),\n\n        // Muted / Skeleton\n        \"muted\" => (\"Muted\", \"Background\", \"muted.background\"),\n        \"muted_foreground\" => (\"Muted\", \"Foreground\", \"muted.foreground\"),\n        \"skeleton\" => (\"Skeleton\", \"Background\", \"skeleton.background\"),\n\n        // Charts\n        \"chart_1\" => (\"Chart\", \"Color 1\", \"chart.1\"),\n        \"chart_2\" => (\"Chart\", \"Color 2\", \"chart.2\"),\n        \"chart_3\" => (\"Chart\", \"Color 3\", \"chart.3\"),\n        \"chart_4\" => (\"Chart\", \"Color 4\", \"chart.4\"),\n        \"chart_5\" => (\"Chart\", \"Color 5\", \"chart.5\"),\n\n        // Danger / Success / Warning / Info\n        \"danger\" => (\"Danger\", \"Background\", \"danger.background\"),\n        \"danger_active\" => (\"Danger\", \"Active\", \"danger.active.background\"),\n        \"danger_foreground\" => (\"Danger\", \"Foreground\", \"danger.foreground\"),\n        \"danger_hover\" => (\"Danger\", \"Hover\", \"danger.hover.background\"),\n\n        \"success\" => (\"Success\", \"Background\", \"success.background\"),\n        \"success_active\" => (\"Success\", \"Active\", \"success.active.background\"),\n        \"success_foreground\" => (\"Success\", \"Foreground\", \"success.foreground\"),\n        \"success_hover\" => (\"Success\", \"Hover\", \"success.hover.background\"),\n\n        \"warning\" => (\"Warning\", \"Background\", \"warning.background\"),\n        \"warning_active\" => (\"Warning\", \"Active\", \"warning.active.background\"),\n        \"warning_foreground\" => (\"Warning\", \"Foreground\", \"warning.foreground\"),\n        \"warning_hover\" => (\"Warning\", \"Hover\", \"warning.hover.background\"),\n\n        \"info\" => (\"Info\", \"Background\", \"info.background\"),\n        \"info_active\" => (\"Info\", \"Active\", \"info.active.background\"),\n        \"info_foreground\" => (\"Info\", \"Foreground\", \"info.foreground\"),\n        \"info_hover\" => (\"Info\", \"Hover\", \"info.hover.background\"),\n\n        // Base Colors\n        \"red\" => (\"Base\", \"Red\", \"base.red\"),\n        \"red_light\" => (\"Base\", \"Red Light\", \"base.red.light\"),\n        \"green\" => (\"Base\", \"Green\", \"base.green\"),\n        \"green_light\" => (\"Base\", \"Green Light\", \"base.green.light\"),\n        \"blue\" => (\"Base\", \"Blue\", \"base.blue\"),\n        \"blue_light\" => (\"Base\", \"Blue Light\", \"base.blue.light\"),\n        \"yellow\" => (\"Base\", \"Yellow\", \"base.yellow\"),\n        \"yellow_light\" => (\"Base\", \"Yellow Light\", \"base.yellow.light\"),\n        \"magenta\" => (\"Base\", \"Magenta\", \"base.magenta\"),\n        \"magenta_light\" => (\"Base\", \"Magenta Light\", \"base.magenta.light\"),\n        \"cyan\" => (\"Base\", \"Cyan\", \"base.cyan\"),\n        \"cyan_light\" => (\"Base\", \"Cyan Light\", \"base.cyan.light\"),\n\n        // Everything else remains in Global or attempts a split\n        _ => {\n            if key.contains('_') {\n                let parts: Vec<&str> = key.splitn(2, '_').collect();\n                (parts[0], parts[1], key)\n            } else {\n                (\"Global\", key, key)\n            }\n        }\n    };\n\n    ParsedKey {\n        category: to_title_case_full(category),\n        name: to_title_case_full(name),\n        canonical_key: canonical.to_string(),\n    }\n}\n\nfn to_title_case(s: &str) -> String {\n    let mut c = s.chars();\n    match c.next() {\n        None => String::new(),\n        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),\n    }\n}\n\nfn to_title_case_full(s: &str) -> String {\n    s.split(|c| c == '_' || c == '.')\n        .map(to_title_case)\n        .collect::<Vec<_>>()\n        .join(\" \")\n}\n"
  },
  {
    "path": "crates/story/src/stories/theme_story/mod.rs",
    "content": "mod checkerboard;\nmod color_theme_story;\nmod mapper;\n\npub use color_theme_story::*;"
  },
  {
    "path": "crates/story/src/stories/toggle_story.rs",
    "content": "use gpui::{\n    App, AppContext as _, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement as _,\n    Render, Styled as _, Window,\n};\n\nuse gpui_component::{\n    IconName, Sizable, StyledExt,\n    button::{Toggle, ToggleGroup, ToggleVariants},\n    v_flex,\n};\n\nuse crate::section;\n\npub struct ToggleStory {\n    focus_handle: FocusHandle,\n    single_toggle: usize,\n    checked: Vec<bool>,\n}\n\nimpl ToggleStory {\n    pub fn view(_: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self {\n            focus_handle: cx.focus_handle(),\n            single_toggle: 0,\n            checked: vec![false; 20],\n        })\n    }\n}\n\nimpl super::Story for ToggleStory {\n    fn title() -> &'static str {\n        \"ToggleButton\"\n    }\n\n    fn description() -> &'static str {\n        \"\"\n    }\n\n    fn closable() -> bool {\n        false\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl Focusable for ToggleStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for ToggleStory {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .w_full()\n            .gap_3()\n            .child(\n                section(\"Toggle\")\n                    .child(\n                        Toggle::new(\"item1\")\n                            .label(\"Single Toggle Item 1\")\n                            .large()\n                            .checked(self.single_toggle == 1)\n                            .on_click(cx.listener(|view, checked, _, cx| {\n                                if *checked {\n                                    view.single_toggle = 1;\n                                }\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Toggle::new(\"item2\")\n                            .label(\"Single Toggle Item 2\")\n                            .large()\n                            .checked(self.single_toggle == 2)\n                            .on_click(cx.listener(|view, checked, _, cx| {\n                                if *checked {\n                                    view.single_toggle = 2;\n                                }\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Toggle::new(\"item3\")\n                            .icon(IconName::Eye)\n                            .large()\n                            .checked(self.single_toggle == 3)\n                            .on_click(cx.listener(|view, checked, _, cx| {\n                                if *checked {\n                                    view.single_toggle = 3;\n                                }\n                                cx.notify();\n                            })),\n                    ),\n            )\n            .child(\n                section(\"Toggle Group with Ghost Style\")\n                    .v_flex()\n                    .gap_4()\n                    .child(\n                        ToggleGroup::new(\"toggle-button-group1\")\n                            .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0]))\n                            .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1]))\n                            .child(\n                                Toggle::new(2)\n                                    .icon(IconName::Inbox)\n                                    .checked(self.checked[2]),\n                            )\n                            .child(\n                                Toggle::new(3)\n                                    .icon(IconName::Check)\n                                    .checked(self.checked[3]),\n                            )\n                            .child(Toggle::new(4).label(\"Other\").checked(self.checked[4]))\n                            .on_click(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {\n                                view.checked[0] = checkeds[0];\n                                view.checked[1] = checkeds[1];\n                                view.checked[2] = checkeds[2];\n                                view.checked[3] = checkeds[3];\n                                view.checked[4] = checkeds[4];\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        ToggleGroup::new(\"toggle-button-group1-sm\")\n                            .small()\n                            .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0]))\n                            .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1]))\n                            .child(\n                                Toggle::new(2)\n                                    .icon(IconName::Inbox)\n                                    .checked(self.checked[2]),\n                            )\n                            .child(\n                                Toggle::new(3)\n                                    .icon(IconName::Check)\n                                    .checked(self.checked[3]),\n                            )\n                            .child(Toggle::new(4).label(\"Other\").checked(self.checked[4]))\n                            .on_click(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {\n                                view.checked[0] = checkeds[0];\n                                view.checked[1] = checkeds[1];\n                                view.checked[2] = checkeds[2];\n                                view.checked[3] = checkeds[3];\n                                view.checked[4] = checkeds[4];\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        ToggleGroup::new(\"toggle-button-group1-xs\")\n                            .xsmall()\n                            .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0]))\n                            .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1]))\n                            .child(\n                                Toggle::new(2)\n                                    .icon(IconName::Inbox)\n                                    .checked(self.checked[2]),\n                            )\n                            .child(\n                                Toggle::new(3)\n                                    .icon(IconName::Check)\n                                    .checked(self.checked[3]),\n                            )\n                            .child(Toggle::new(4).label(\"Other\").checked(self.checked[4]))\n                            .on_click(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {\n                                view.checked[0] = checkeds[0];\n                                view.checked[1] = checkeds[1];\n                                view.checked[2] = checkeds[2];\n                                view.checked[3] = checkeds[3];\n                                view.checked[4] = checkeds[4];\n                                cx.notify();\n                            })),\n                    ),\n            )\n            .child(\n                section(\"Toggle Group with Outline Style\")\n                    .v_flex()\n                    .gap_4()\n                    .child(\n                        ToggleGroup::new(\"toggle-button-group2\")\n                            .outline()\n                            .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0]))\n                            .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1]))\n                            .child(\n                                Toggle::new(2)\n                                    .icon(IconName::Inbox)\n                                    .checked(self.checked[2]),\n                            )\n                            .child(\n                                Toggle::new(3)\n                                    .icon(IconName::Check)\n                                    .checked(self.checked[3]),\n                            )\n                            .child(Toggle::new(4).label(\"Other\").checked(self.checked[4]))\n                            .on_click(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {\n                                view.checked[0] = checkeds[0];\n                                view.checked[1] = checkeds[1];\n                                view.checked[2] = checkeds[2];\n                                view.checked[3] = checkeds[3];\n                                view.checked[4] = checkeds[4];\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        ToggleGroup::new(\"toggle-button-group2-sm\")\n                            .outline()\n                            .small()\n                            .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0]))\n                            .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1]))\n                            .child(\n                                Toggle::new(2)\n                                    .icon(IconName::Inbox)\n                                    .checked(self.checked[2]),\n                            )\n                            .child(\n                                Toggle::new(3)\n                                    .icon(IconName::Check)\n                                    .checked(self.checked[3]),\n                            )\n                            .child(Toggle::new(4).label(\"Other\").checked(self.checked[4]))\n                            .on_click(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {\n                                view.checked[0] = checkeds[0];\n                                view.checked[1] = checkeds[1];\n                                view.checked[2] = checkeds[2];\n                                view.checked[3] = checkeds[3];\n                                view.checked[4] = checkeds[4];\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        ToggleGroup::new(\"toggle-button-group2-xs\")\n                            .outline()\n                            .xsmall()\n                            .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0]))\n                            .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1]))\n                            .child(\n                                Toggle::new(2)\n                                    .icon(IconName::Inbox)\n                                    .checked(self.checked[2]),\n                            )\n                            .child(\n                                Toggle::new(3)\n                                    .icon(IconName::Check)\n                                    .checked(self.checked[3]),\n                            )\n                            .child(Toggle::new(4).label(\"Other\").checked(self.checked[4]))\n                            .on_click(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {\n                                view.checked[0] = checkeds[0];\n                                view.checked[1] = checkeds[1];\n                                view.checked[2] = checkeds[2];\n                                view.checked[3] = checkeds[3];\n                                view.checked[4] = checkeds[4];\n                                cx.notify();\n                            })),\n                    ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/tooltip_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, Focusable, InteractiveElement, KeyBinding, ParentElement,\n    Render, StatefulInteractiveElement, Styled, Window, actions, div,\n};\n\nuse gpui_component::{\n    ActiveTheme, IconName,\n    button::{Button, ButtonVariant, ButtonVariants},\n    checkbox::Checkbox,\n    dock::PanelControl,\n    h_flex,\n    radio::Radio,\n    switch::Switch,\n    tooltip::Tooltip,\n    v_flex,\n};\n\nuse crate::{Story, section};\n\nactions!(tooltip_story, [Info]);\n\npub fn init(cx: &mut App) {\n    cx.bind_keys([KeyBinding::new(\"ctrl-shift-delete\", Info, Some(\"Tooltip\"))]);\n}\n\npub struct TooltipStory {\n    focus_handle: gpui::FocusHandle,\n}\n\nimpl TooltipStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n}\n\nimpl Story for TooltipStory {\n    fn title() -> &'static str {\n        \"Tooltip\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n\n    fn zoomable() -> Option<PanelControl> {\n        None\n    }\n}\n\nimpl Focusable for TooltipStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for TooltipStory {\n    fn render(\n        &mut self,\n        _: &mut gpui::Window,\n        _cx: &mut gpui::Context<Self>,\n    ) -> impl gpui::IntoElement {\n        v_flex()\n            .w_full()\n            .gap_3()\n            .child(\n                section(\"Tooltip for Button\")\n                    .child(\n                        Button::new(\"btn0\")\n                            .label(\"Search\")\n                            .with_variant(ButtonVariant::Primary)\n                            .tooltip(\"This is a search Button.\"),\n                    )\n                    .child(Button::new(\"btn1\").label(\"Info\").tooltip_with_action(\n                        \"This is a tooltip with Action for display keybinding.\",\n                        &Info,\n                        Some(\"Tooltip\"),\n                    ))\n                    .child(\n                        div()\n                            .child(Button::new(\"btn3\").label(\"Hover me\"))\n                            .id(\"tooltip-4\")\n                            .tooltip(|window, cx| {\n                                Tooltip::element(|_, cx| {\n                                    h_flex()\n                                        .gap_x_1()\n                                        .child(IconName::Info)\n                                        .child(\n                                            div()\n                                                .child(\"Muted Foreground\")\n                                                .text_color(cx.theme().muted_foreground),\n                                        )\n                                        .child(div().child(\"Danger\").text_color(cx.theme().danger))\n                                        .child(IconName::ArrowUp)\n                                })\n                                .build(window, cx)\n                            }),\n                    ),\n            )\n            .child(\n                section(\"Label Tooltip\").child(div().child(\"Hover me\").id(\"tooltip-2\").tooltip(\n                    |window, cx| {\n                        Tooltip::new(\"This is a Label\")\n                            .action(&Info, Some(\"Tooltip\"))\n                            .build(window, cx)\n                    },\n                )),\n            )\n            .child(\n                section(\"Checkbox Tooltip\").child(\n                    Checkbox::new(\"check\")\n                        .label(\"Remember me\")\n                        .checked(true)\n                        .tooltip(|window, cx| Tooltip::new(\"This is a checkbox\").build(window, cx)),\n                ),\n            )\n            .child(\n                section(\"Radio Tooltip\").child(\n                    Radio::new(\"radio\")\n                        .label(\"Radio with tooltip\")\n                        .checked(true)\n                        .tooltip(|window, cx| {\n                            Tooltip::new(\"This is a radio button\").build(window, cx)\n                        }),\n                ),\n            )\n            .child(\n                section(\"Switch Tooltip\").child(\n                    Switch::new(\"switch\")\n                        .checked(true)\n                        .tooltip(\"This is a switch\"),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/tree_story.rs",
    "content": "use std::path::PathBuf;\n\nuse autocorrect::ignorer::Ignorer;\nuse gpui::{\n    App, AppContext, Context, Entity, InteractiveElement, KeyBinding, ParentElement, Render,\n    Styled, Window, actions, px,\n};\n\nuse gpui_component::{\n    ActiveTheme as _, IconName, StyledExt as _,\n    button::Button,\n    dock::PanelControl,\n    h_flex,\n    label::Label,\n    list::ListItem,\n    tree::{TreeItem, TreeState, tree},\n    v_flex,\n};\nuse rand::seq::SliceRandom as _;\n\nuse crate::{Story, section};\n\nactions!(story, [Rename]);\n\nconst CONTEXT: &str = \"TreeStory\";\npub(crate) fn init(cx: &mut App) {\n    cx.bind_keys([KeyBinding::new(\"enter\", Rename, Some(CONTEXT))]);\n}\n\npub struct TreeStory {\n    tree_state: Entity<TreeState>,\n    items: Vec<TreeItem>,\n}\n\nfn build_file_items(ignorer: &Ignorer, root: &PathBuf, path: &PathBuf) -> Vec<TreeItem> {\n    let mut items = Vec::new();\n    if let Ok(entries) = std::fs::read_dir(path) {\n        for entry in entries.flatten() {\n            let path = entry.path();\n            let relative_path = path.strip_prefix(root).unwrap_or(&path);\n            if ignorer.is_ignored(&relative_path.to_string_lossy())\n                || relative_path.ends_with(\".git\")\n            {\n                continue;\n            }\n            let file_name = path\n                .file_name()\n                .and_then(|n| n.to_str())\n                .unwrap_or(\"Unknown\")\n                .to_string();\n            let id = path.to_string_lossy().to_string();\n            if path.is_dir() {\n                let children = build_file_items(ignorer, &root, &path);\n                items.push(TreeItem::new(id, file_name).children(children));\n            } else {\n                items.push(TreeItem::new(id, file_name));\n            }\n        }\n    }\n    items.sort_by(|a, b| {\n        b.is_folder()\n            .cmp(&a.is_folder())\n            .then(a.label.cmp(&b.label))\n    });\n    items\n}\n\nimpl TreeStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn load_files(state: Entity<TreeState>, path: PathBuf, cx: &mut Context<Self>) {\n        cx.spawn(async move |weak_self, cx| {\n            let ignorer = Ignorer::new(&path.to_string_lossy());\n            let items = build_file_items(&ignorer, &path, &path);\n            _ = state.update(cx, |state, cx| {\n                state.set_items(items.clone(), cx);\n            });\n\n            _ = weak_self.update(cx, |this, cx| {\n                this.items = items;\n                cx.notify();\n            })\n        })\n        .detach();\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        let tree_state = cx.new(|cx| TreeState::new(cx));\n\n        Self::load_files(tree_state.clone(), PathBuf::from(\"./\"), cx);\n\n        Self {\n            tree_state,\n            items: Vec::new(),\n        }\n    }\n\n    fn on_action_rename(&mut self, _: &Rename, _: &mut Window, cx: &mut gpui::Context<Self>) {\n        if let Some(entry) = self.tree_state.read(cx).selected_entry() {\n            let item = entry.item();\n            println!(\"Renaming item: {} ({})\", item.label, item.id);\n            // Here you could implement actual renaming logic\n        }\n    }\n}\n\nimpl Story for TreeStory {\n    fn title() -> &'static str {\n        \"Tree\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n\n    fn zoomable() -> Option<PanelControl> {\n        None\n    }\n}\n\nimpl Render for TreeStory {\n    fn render(\n        &mut self,\n        _: &mut gpui::Window,\n        cx: &mut gpui::Context<Self>,\n    ) -> impl gpui::IntoElement {\n        let view = cx.entity();\n        v_flex()\n            .w_full()\n            .gap_3()\n            .id(\"tree-story\")\n            .key_context(CONTEXT)\n            .on_action(cx.listener(Self::on_action_rename))\n            .child(\n                h_flex().gap_3().child(\n                    Button::new(\"select-item\")\n                        .outline()\n                        .label(\"Select Item\")\n                        .on_click(cx.listener(|this, _, _, cx| {\n                            if let Some(random_item) = this.items.choose(&mut rand::thread_rng()) {\n                                this.tree_state.update(cx, |state, cx| {\n                                    state.set_selected_item(Some(random_item), cx);\n                                });\n                            }\n                        })),\n                ),\n            )\n            .child(\n                section(\"File tree\")\n                    .sub_title(\"Press `space` to select, `enter` to rename.\")\n                    .v_flex()\n                    .max_w_md()\n                    .child(\n                        tree(\n                            &self.tree_state,\n                            move |ix, entry, _selected, _window, cx| {\n                                view.update(cx, |_, cx| {\n                                    let item = entry.item();\n                                    let icon = if !entry.is_folder() {\n                                        IconName::File\n                                    } else if entry.is_expanded() {\n                                        IconName::FolderOpen\n                                    } else {\n                                        IconName::Folder\n                                    };\n\n                                    ListItem::new(ix)\n                                        .w_full()\n                                        .rounded(cx.theme().radius)\n                                        .px_3()\n                                        .pl(px(16.) * entry.depth() + px(12.))\n                                        .child(\n                                            h_flex().gap_2().child(icon).child(item.label.clone()),\n                                        )\n                                        .on_click(cx.listener({\n                                            let item = item.clone();\n                                            move |_, _, _window, _| {\n                                                println!(\n                                                    \"Clicked on item: {} ({})\",\n                                                    item.label, item.id\n                                                );\n                                            }\n                                        }))\n                                })\n                            },\n                        )\n                        .p_1()\n                        .border_1()\n                        .border_color(cx.theme().border)\n                        .rounded(cx.theme().radius)\n                        .h(px(540.)),\n                    )\n                    .child(\n                        h_flex()\n                            .w_full()\n                            .justify_between()\n                            .gap_3()\n                            .children(\n                                self.tree_state\n                                    .read(cx)\n                                    .selected_index()\n                                    .map(|ix| format!(\"Selected Index: {}\", ix)),\n                            )\n                            .children(\n                                self.tree_state\n                                    .read(cx)\n                                    .selected_item()\n                                    .map(|item| Label::new(\"Selected:\").secondary(item.id.clone())),\n                            ),\n                    ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/virtual_list_story.rs",
    "content": "use std::{ops::Range, rc::Rc};\n\nuse gpui::{\n    App, AppContext, Context, Div, Entity, FocusHandle, Focusable, InteractiveElement, IntoElement,\n    ParentElement, Pixels, Render, ScrollStrategy, Size, Styled, Window, div, px, size,\n};\nuse gpui_component::{\n    ActiveTheme as _, Selectable, Sizable, VirtualListScrollHandle,\n    button::{Button, ButtonGroup},\n    divider::Divider,\n    h_flex,\n    scroll::{ScrollableElement, ScrollbarAxis},\n    v_flex, v_virtual_list,\n};\n\npub struct VirtualListStory {\n    focus_handle: FocusHandle,\n    scroll_handle: VirtualListScrollHandle,\n    items: Vec<String>,\n    item_sizes: Rc<Vec<Size<Pixels>>>,\n    columns_count: usize,\n    axis: ScrollbarAxis,\n    size_mode: usize,\n    visible_range: Range<usize>,\n}\n\nconst ITEM_SIZE: Size<Pixels> = size(px(100.), px(30.));\n\nimpl VirtualListStory {\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        let items = (0..5000).map(|i| format!(\"Item {}\", i)).collect::<Vec<_>>();\n        let item_sizes = items.iter().map(|_| ITEM_SIZE).collect::<Vec<_>>();\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            scroll_handle: VirtualListScrollHandle::new(),\n            items,\n            item_sizes: Rc::new(item_sizes),\n            columns_count: 100,\n            axis: ScrollbarAxis::Both,\n            size_mode: 0,\n            visible_range: (0..0),\n        }\n    }\n\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    pub fn change_test_cases(&mut self, n: usize, cx: &mut Context<Self>) {\n        self.size_mode = n;\n        if n == 0 {\n            self.items = (0..5000).map(|i| format!(\"Item {}\", i)).collect::<Vec<_>>();\n            self.columns_count = 30;\n        } else if n == 1 {\n            self.items = (0..100).map(|i| format!(\"Item {}\", i)).collect::<Vec<_>>();\n            self.columns_count = 100;\n        } else if n == 2 {\n            self.items = (0..500000)\n                .map(|i| format!(\"Item {}\", i))\n                .collect::<Vec<_>>();\n            self.columns_count = 100;\n        } else {\n            self.items = (0..5).map(|i| format!(\"Item {}\", i)).collect::<Vec<_>>();\n            self.columns_count = 10;\n        }\n\n        self.item_sizes = Rc::new(self.items.iter().map(|_| ITEM_SIZE).collect());\n        cx.notify();\n    }\n\n    pub fn change_axis(&mut self, axis: ScrollbarAxis, cx: &mut Context<Self>) {\n        self.axis = axis;\n        cx.notify();\n    }\n\n    fn render_buttons(&mut self, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_2()\n            .child(\n                h_flex()\n                    .gap_2()\n                    .justify_between()\n                    .child(\n                        h_flex()\n                            .gap_2()\n                            .child(\n                                ButtonGroup::new(\"test-cases\")\n                                    .outline()\n                                    .compact()\n                                    .child(\n                                        Button::new(\"test-0\")\n                                            .label(\"Size 0\")\n                                            .selected(self.size_mode == 0),\n                                    )\n                                    .child(\n                                        Button::new(\"test-1\")\n                                            .label(\"Size 1\")\n                                            .selected(self.size_mode == 1),\n                                    )\n                                    .child(\n                                        Button::new(\"test-2\")\n                                            .label(\"Size 2\")\n                                            .selected(self.size_mode == 2),\n                                    )\n                                    .child(\n                                        Button::new(\"test-3\")\n                                            .label(\"Size 3\")\n                                            .selected(self.size_mode == 3),\n                                    )\n                                    .on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {\n                                        if clicks.contains(&0) {\n                                            view.change_test_cases(0, cx)\n                                        } else if clicks.contains(&1) {\n                                            view.change_test_cases(1, cx)\n                                        } else if clicks.contains(&2) {\n                                            view.change_test_cases(2, cx)\n                                        } else if clicks.contains(&3) {\n                                            view.change_test_cases(3, cx)\n                                        }\n                                    })),\n                            )\n                            .child(Divider::vertical().px_2())\n                            .child(\n                                ButtonGroup::new(\"scrollbars\")\n                                    .outline()\n                                    .compact()\n                                    .child(\n                                        Button::new(\"test-axis-both\")\n                                            .label(\"Both Scrollbar\")\n                                            .selected(self.axis.is_both()),\n                                    )\n                                    .child(\n                                        Button::new(\"test-axis-vertical\")\n                                            .label(\"Vertical\")\n                                            .selected(self.axis.is_vertical()),\n                                    )\n                                    .child(\n                                        Button::new(\"test-axis-horizontal\")\n                                            .label(\"Horizontal\")\n                                            .selected(self.axis.is_horizontal()),\n                                    )\n                                    .on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {\n                                        if clicks.contains(&0) {\n                                            view.change_axis(ScrollbarAxis::Both, cx)\n                                        } else if clicks.contains(&1) {\n                                            view.change_axis(ScrollbarAxis::Vertical, cx)\n                                        } else if clicks.contains(&2) {\n                                            view.change_axis(ScrollbarAxis::Horizontal, cx)\n                                        }\n                                    })),\n                            ),\n                    )\n                    .child(format!(\"visible_range: {:?}\", self.visible_range)),\n            )\n            .child(\n                h_flex()\n                    .gap_2()\n                    .child(\n                        Button::new(\"scroll-to0\")\n                            .small()\n                            .outline()\n                            .label(\"Scroll to Top\")\n                            .on_click(cx.listener(|this, _, _, cx| {\n                                this.scroll_handle.scroll_to_item(0, ScrollStrategy::Top);\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Button::new(\"scroll-to1\")\n                            .small()\n                            .outline()\n                            .label(\"Scroll to 50\")\n                            .on_click(cx.listener(|this, _, _, cx| {\n                                this.scroll_handle.scroll_to_item(50, ScrollStrategy::Top);\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Button::new(\"scroll-to2\")\n                            .small()\n                            .outline()\n                            .label(\"Scroll to 25 (center)\")\n                            .on_click(cx.listener(|this, _, _, cx| {\n                                this.scroll_handle\n                                    .scroll_to_item(25, ScrollStrategy::Center);\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Button::new(\"scroll-to-bottom\")\n                            .small()\n                            .outline()\n                            .label(\"Scroll to Bottom\")\n                            .on_click(cx.listener(|this, _, _, cx| {\n                                this.scroll_handle.scroll_to_bottom();\n                                cx.notify();\n                            })),\n                    ),\n            )\n    }\n}\n\nimpl super::Story for VirtualListStory {\n    fn title() -> &'static str {\n        \"VirtualList\"\n    }\n\n    fn description() -> &'static str {\n        \"Add vertical or horizontal, or both scrollbars to a container, \\\n        and use `virtual_list` to render a large number of items.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n}\n\nimpl Focusable for VirtualListStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for VirtualListStory {\n    fn render(\n        &mut self,\n        _: &mut gpui::Window,\n        cx: &mut gpui::Context<Self>,\n    ) -> impl gpui::IntoElement {\n        let columns_count = self.columns_count;\n\n        fn render_item(cx: &App) -> Div {\n            div()\n                .flex()\n                .h_full()\n                .items_center()\n                .justify_center()\n                .text_sm()\n                .w(ITEM_SIZE.width)\n                .h(ITEM_SIZE.height)\n                .bg(cx.theme().secondary)\n        }\n\n        v_flex()\n            .size_full()\n            .gap_4()\n            .child(self.render_buttons(cx))\n            .child(\n                div().w_full().flex_1().min_h_64().child(\n                    div().relative().size_full().child(\n                        v_flex()\n                            .id(\"list\")\n                            .relative()\n                            .size_full()\n                            .child(\n                                v_virtual_list(\n                                    cx.entity().clone(),\n                                    \"items\",\n                                    self.item_sizes.clone(),\n                                    move |story, visible_range, _, cx| {\n                                        story.visible_range = visible_range.clone();\n\n                                        visible_range\n                                            .map(|ix| {\n                                                h_flex().gap_1().items_center().children(\n                                                    (0..columns_count).map(|i| {\n                                                        render_item(cx).child(if i == 0 {\n                                                            format!(\"row: {}\", ix)\n                                                        } else {\n                                                            format!(\"{}\", i)\n                                                        })\n                                                    }),\n                                                )\n                                            })\n                                            .collect()\n                                    },\n                                )\n                                .track_scroll(&self.scroll_handle)\n                                .p_4()\n                                .border_1()\n                                .border_color(cx.theme().border)\n                                .gap_1(),\n                            )\n                            .scrollbar(&self.scroll_handle, self.axis),\n                    ),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story/src/stories/welcome_story.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Entity, FocusHandle, Focusable, Render, Styled as _, Window, px,\n};\n\nuse gpui_component::{dock::PanelControl, text::markdown};\n\nuse crate::Story;\n\npub struct WelcomeStory {\n    focus_handle: FocusHandle,\n}\n\nimpl WelcomeStory {\n    pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| Self::new(window, cx))\n    }\n\n    fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n}\n\nimpl Story for WelcomeStory {\n    fn title() -> &'static str {\n        \"Introduction\"\n    }\n\n    fn description() -> &'static str {\n        \"UI components for building fantastic desktop application by using GPUI.\"\n    }\n\n    fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {\n        Self::view(window, cx)\n    }\n\n    fn zoomable() -> Option<PanelControl> {\n        None\n    }\n\n    fn paddings() -> gpui::Pixels {\n        px(0.)\n    }\n}\n\nimpl Focusable for WelcomeStory {\n    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for WelcomeStory {\n    fn render(\n        &mut self,\n        _: &mut gpui::Window,\n        _: &mut gpui::Context<Self>,\n    ) -> impl gpui::IntoElement {\n        markdown(include_str!(\"../../../../README.md\"))\n            .px_4()\n            .scrollable(true)\n            .selectable(true)\n    }\n}\n"
  },
  {
    "path": "crates/story/src/themes.rs",
    "content": "use gpui::{Action, App, SharedString};\nuse gpui_component::{Theme, ThemeMode, ThemeRegistry, scroll::ScrollbarShow};\nuse serde::{Deserialize, Serialize};\n\n#[cfg(not(target_family = \"wasm\"))]\nuse gpui_component::ActiveTheme;\n\n#[cfg(target_family = \"wasm\")]\nuse crate::embedded_themes;\n\nconst STATE_FILE: &str = \"target/state.json\";\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\nstruct State {\n    theme: SharedString,\n    scrollbar_show: Option<ScrollbarShow>,\n}\n\nimpl Default for State {\n    fn default() -> Self {\n        Self {\n            theme: \"Default Light\".into(),\n            scrollbar_show: None,\n        }\n    }\n}\n\npub fn init(cx: &mut App) {\n    #[cfg(target_family = \"wasm\")]\n    {\n        tracing::info!(\"Loading embedded themes for WASM...\");\n        let embedded = embedded_themes::embedded_themes();\n        let registry = ThemeRegistry::global_mut(cx);\n\n        for (name, content) in embedded {\n            if let Err(e) = registry.load_themes_from_str(content) {\n                tracing::error!(\"Failed to load embedded theme {}: {}\", name, e);\n            } else {\n                tracing::info!(\"Loaded embedded theme: {}\", name);\n            }\n        }\n    }\n\n    let state = if cfg!(not(target_family = \"wasm\")) {\n        let json = std::fs::read_to_string(STATE_FILE).unwrap_or(String::default());\n        serde_json::from_str::<State>(&json).unwrap_or_default()\n    } else {\n        State::default()\n    };\n\n    #[cfg(not(target_family = \"wasm\"))]\n    if let Err(err) =\n        ThemeRegistry::watch_dir(std::path::PathBuf::from(\"./themes\"), cx, move |cx| {\n            if let Some(theme) = ThemeRegistry::global(cx)\n                .themes()\n                .get(&state.theme)\n                .cloned()\n            {\n                Theme::global_mut(cx).apply_config(&theme);\n            }\n        })\n    {\n        tracing::error!(\"Failed to watch themes directory: {}\", err);\n    }\n\n    if let Some(scrollbar_show) = state.scrollbar_show {\n        Theme::global_mut(cx).scrollbar_show = scrollbar_show;\n    }\n    cx.refresh_windows();\n\n    #[cfg(not(target_family = \"wasm\"))]\n    cx.observe_global::<Theme>(|cx| {\n        let state = State {\n            theme: cx.theme().theme_name().clone(),\n            scrollbar_show: Some(cx.theme().scrollbar_show),\n        };\n\n        if let Ok(json) = serde_json::to_string_pretty(&state) {\n            // Ignore write errors - if STATE_FILE doesn't exist or can't be written, do nothing\n            let _ = std::fs::write(STATE_FILE, json);\n        }\n    })\n    .detach();\n\n    cx.on_action(|switch: &SwitchTheme, cx| {\n        let theme_name = switch.0.clone();\n        if let Some(theme_config) = ThemeRegistry::global(cx).themes().get(&theme_name).cloned() {\n            Theme::global_mut(cx).apply_config(&theme_config);\n        }\n        cx.refresh_windows();\n    });\n    cx.on_action(|switch: &SwitchThemeMode, cx| {\n        let mode = switch.0;\n        Theme::change(mode, None, cx);\n        cx.refresh_windows();\n    });\n}\n\n#[derive(Action, Clone, PartialEq)]\n#[action(namespace = themes, no_json)]\npub(crate) struct SwitchTheme(pub(crate) SharedString);\n\n#[derive(Action, Clone, PartialEq)]\n#[action(namespace = themes, no_json)]\npub(crate) struct SwitchThemeMode(pub(crate) ThemeMode);\n"
  },
  {
    "path": "crates/story/src/title_bar.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{\n    AnyElement, App, AppContext, Context, Corner, Entity, FocusHandle, InteractiveElement as _,\n    IntoElement, MouseButton, ParentElement as _, Render, SharedString, Styled as _, Subscription,\n    Window, div, px,\n};\nuse gpui_component::{\n    ActiveTheme as _, IconName, Side, Sizable as _, Theme, TitleBar, WindowExt as _,\n    badge::Badge,\n    button::{Button, ButtonVariants as _},\n    label::Label,\n    menu::{AppMenuBar, DropdownMenu as _},\n    scroll::ScrollbarShow,\n};\n\nuse crate::{SelectFont, SelectRadius, SelectScrollbarShow, ToggleListActiveHighlight, app_menus};\n\npub struct AppTitleBar {\n    app_menu_bar: Entity<AppMenuBar>,\n    font_size_selector: Entity<FontSizeSelector>,\n    child: Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl AppTitleBar {\n    pub fn new(\n        title: impl Into<SharedString>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Self {\n        let app_menu_bar = app_menus::init(title, cx);\n        let font_size_selector = cx.new(|cx| FontSizeSelector::new(window, cx));\n\n        Self {\n            app_menu_bar,\n            font_size_selector,\n            child: Rc::new(|_, _| div().into_any_element()),\n            _subscriptions: vec![],\n        }\n    }\n\n    pub fn child<F, E>(mut self, f: F) -> Self\n    where\n        E: IntoElement,\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n    {\n        self.child = Rc::new(move |window, cx| f(window, cx).into_any_element());\n        self\n    }\n}\n\nimpl Render for AppTitleBar {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let notifications_count = window.notifications(cx).len();\n\n        TitleBar::new()\n            // left side\n            .child(div().flex().items_center().child(self.app_menu_bar.clone()))\n            .child(\n                div()\n                    .flex()\n                    .items_center()\n                    .justify_end()\n                    .px_2()\n                    .gap_2()\n                    .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())\n                    .child((self.child.clone())(window, cx))\n                    .child(\n                        Label::new(\"theme:\")\n                            .secondary(cx.theme().theme_name())\n                            .text_sm(),\n                    )\n                    .child(self.font_size_selector.clone())\n                    .child(\n                        Button::new(\"github\")\n                            .icon(IconName::Github)\n                            .small()\n                            .ghost()\n                            .on_click(|_, _, cx| {\n                                cx.open_url(\"https://github.com/longbridge/gpui-component\")\n                            }),\n                    )\n                    .child(\n                        div().relative().child(\n                            Badge::new().count(notifications_count).max(99).child(\n                                Button::new(\"bell\")\n                                    .small()\n                                    .ghost()\n                                    .compact()\n                                    .icon(IconName::Bell),\n                            ),\n                        ),\n                    ),\n            )\n    }\n}\n\nstruct FontSizeSelector {\n    focus_handle: FocusHandle,\n}\n\nimpl FontSizeSelector {\n    pub fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n\n    fn on_select_font(\n        &mut self,\n        font_size: &SelectFont,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        Theme::global_mut(cx).font_size = px(font_size.0 as f32);\n        window.refresh();\n    }\n\n    fn on_select_radius(\n        &mut self,\n        radius: &SelectRadius,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        Theme::global_mut(cx).radius = px(radius.0 as f32);\n        Theme::global_mut(cx).radius_lg = if cx.theme().radius > px(0.) {\n            cx.theme().radius + px(2.)\n        } else {\n            px(0.)\n        };\n        window.refresh();\n    }\n\n    fn on_select_scrollbar_show(\n        &mut self,\n        show: &SelectScrollbarShow,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        Theme::global_mut(cx).scrollbar_show = show.0;\n        window.refresh();\n    }\n\n    fn on_toggle_list_active_highlight(\n        &mut self,\n        _: &ToggleListActiveHighlight,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let theme = Theme::global_mut(cx);\n        theme.list.active_highlight = !theme.list.active_highlight;\n        window.refresh();\n    }\n}\n\nimpl Render for FontSizeSelector {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let focus_handle = self.focus_handle.clone();\n        let font_size = cx.theme().font_size.as_f32() as i32;\n        let radius = cx.theme().radius.as_f32() as i32;\n        let scroll_show = cx.theme().scrollbar_show;\n\n        div()\n            .id(\"font-size-selector\")\n            .track_focus(&focus_handle)\n            .on_action(cx.listener(Self::on_select_font))\n            .on_action(cx.listener(Self::on_select_radius))\n            .on_action(cx.listener(Self::on_select_scrollbar_show))\n            .on_action(cx.listener(Self::on_toggle_list_active_highlight))\n            .child(\n                Button::new(\"btn\")\n                    .small()\n                    .ghost()\n                    .icon(IconName::Settings2)\n                    .dropdown_menu(move |this, _, cx| {\n                        this.scrollable(true)\n                            .check_side(Side::Right)\n                            .max_h(px(480.))\n                            .label(\"Font Size\")\n                            .menu_with_check(\"Large\", font_size == 18, Box::new(SelectFont(18)))\n                            .menu_with_check(\n                                \"Medium (default)\",\n                                font_size == 16,\n                                Box::new(SelectFont(16)),\n                            )\n                            .menu_with_check(\"Small\", font_size == 14, Box::new(SelectFont(14)))\n                            .separator()\n                            .label(\"Border Radius\")\n                            .menu_with_check(\"8px\", radius == 8, Box::new(SelectRadius(8)))\n                            .menu_with_check(\n                                \"6px (default)\",\n                                radius == 6,\n                                Box::new(SelectRadius(6)),\n                            )\n                            .menu_with_check(\"4px\", radius == 4, Box::new(SelectRadius(4)))\n                            .menu_with_check(\"0px\", radius == 0, Box::new(SelectRadius(0)))\n                            .separator()\n                            .label(\"Scrollbar\")\n                            .menu_with_check(\n                                \"Scrolling to show\",\n                                scroll_show == ScrollbarShow::Scrolling,\n                                Box::new(SelectScrollbarShow(ScrollbarShow::Scrolling)),\n                            )\n                            .menu_with_check(\n                                \"Hover to show\",\n                                scroll_show == ScrollbarShow::Hover,\n                                Box::new(SelectScrollbarShow(ScrollbarShow::Hover)),\n                            )\n                            .menu_with_check(\n                                \"Always show\",\n                                scroll_show == ScrollbarShow::Always,\n                                Box::new(SelectScrollbarShow(ScrollbarShow::Always)),\n                            )\n                            .separator()\n                            .menu_with_check(\n                                \"List Active Highlight\",\n                                cx.theme().list.active_highlight,\n                                Box::new(ToggleListActiveHighlight),\n                            )\n                    })\n                    .anchor(Corner::TopRight),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/story-web/.cargo/config.toml",
    "content": "[target.wasm32-unknown-unknown]\nrustflags = []\n"
  },
  {
    "path": "crates/story-web/Cargo.toml",
    "content": "[package]\nname = \"gpui-component-story-web\"\nversion = \"0.5.1\"\npublish = false\nedition.workspace = true\n\n[lib]\ncrate-type = [\"cdylib\", \"rlib\"]\n\n[dependencies]\ngpui.workspace = true\ngpui_platform.workspace = true\ngpui-component = { path = \"../ui\", default-features = false }\ngpui-component-assets.workspace = true\ngpui-component-story = { path = \"../story\", default-features = false }\n\n# Web specific dependencies\nconsole_error_panic_hook = \"0.1\"\nlog.workspace = true\ntracing-wasm = \"0.2\"\nconsole_log = \"1.0\"\nwasm-bindgen = \"0.2\"\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "crates/story-web/Makefile",
    "content": ".PHONY: help dev build-wasm build-web build clean install\n\nhelp: ## Show help information\n\t@echo \"GPUI Component Story Web - Available commands:\"\n\t@echo \"\"\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = \":.*?## \"}; {printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2}'\n\ninstall: ## Install all dependencies\n\t@echo \"Checking Rust WASM target...\"\n\t@rustup target add wasm32-unknown-unknown || true\n\t@echo \"Checking wasm-bindgen-cli...\"\n\t@cargo install wasm-bindgen-cli || true\n\t@echo \"Installing frontend dependencies...\"\n\t@cd www && bun install\n\nbuild-wasm: ## Build WASM (release mode)\n\t@./scripts/build-wasm.sh --release\n\nbuild-wasm-dev: ## Build WASM (debug mode)\n\t@./scripts/build-wasm.sh\n\nbuild-web: ## Build frontend\n\t@cd www && bun run build\n\nbuild-web-prod: ## Build frontend for production (GitHub Pages)\n\t@cd www && bun install && NODE_ENV=production bun run build\n\nbuild: build-wasm build-web ## Build complete project (WASM + frontend)\n\nbuild-prod: build-wasm build-web-prod ## Build complete project for production\n\ndev: build-wasm-dev ## Start development server\n\t@cd www && bun install && bun run dev\n\npreview: ## Preview production build\n\t@cd www && bun run preview\n\nclean: ## Clean build artifacts\n\t@echo \"Cleaning build artifacts...\"\n\t@rm -rf www/dist\n\t@rm -rf www/src/wasm/*.js www/src/wasm/*.wasm\n\t@cargo clean\n\nwatch-wasm: ## Watch Rust code changes and auto-rebuild WASM\n\t@echo \"Watching WASM changes...\"\n\t@cargo watch -x 'build --target wasm32-unknown-unknown' -s './scripts/build-wasm.sh'\n"
  },
  {
    "path": "crates/story-web/README.md",
    "content": "# GPUI Component Story Web\n\nWeb-based component gallery for GPUI Component library.\n\n**Live Demo**: https://longbridge.github.io/gpui-component/gallery/\n\n## Prerequisites\n\n- Rust toolchain with `wasm32-unknown-unknown` target\n- [Bun](https://bun.sh) (recommended) or Node.js\n- wasm-bindgen-cli\n\n### Install Dependencies\n\n```bash\n# Add WASM target\nrustup target add wasm32-unknown-unknown\n\n# Install wasm-bindgen-cli\ncargo install wasm-bindgen-cli\n\n# Install Bun (macOS/Linux)\ncurl -fsSL https://bun.sh/install | bash\n```\n\n## Development\n\n### Start Development Server\n\n```bash\nmake dev\n```\n\nThis will:\n1. Build WASM in debug mode\n2. Generate JavaScript bindings\n3. Start Vite dev server on http://localhost:3000\n\n## Production Build\n\n### Build for Production\n\n```bash\nmake build-prod\n```\n\nThis builds the project with:\n- Release mode WASM\n- Production optimizations\n- Base path set to `/gpui-component/gallery/` for GitHub Pages\n\nThe output will be in `www/dist/` directory.\n\n## Deployment\n\nThe gallery is automatically deployed to GitHub Pages at `/gpui-component/gallery/` when docs are released.\n\nThe deployment is handled by `.github/workflows/release-docs.yml` which:\n1. Builds WASM in release mode\n2. Builds frontend with production settings\n3. Copies output to `docs/.vitepress/dist/gallery/`\n4. Deploys to GitHub Pages\n"
  },
  {
    "path": "crates/story-web/rust-toolchain.toml",
    "content": "[toolchain]\nchannel = \"nightly\"\ncomponents = [\"rustfmt\", \"clippy\"]\ntargets = [\"wasm32-unknown-unknown\"]\n"
  },
  {
    "path": "crates/story-web/scripts/build-wasm.sh",
    "content": "#!/bin/bash\nset -e\n\n# Colors for output\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nNC='\\033[0m' # No Color\n\necho -e \"${GREEN}Building GPUI Component Story Web...${NC}\"\n\n# Get the script directory\nSCRIPT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nPROJECT_ROOT=\"$SCRIPT_DIR/..\"\n\n# Parse arguments\nRELEASE_FLAG=\"\"\nif [[ \"$1\" == \"--release\" ]]; then\n    RELEASE_FLAG=\"--release\"\n    echo -e \"${YELLOW}Building in release mode${NC}\"\nfi\n\n# Step 1: Build WASM\necho -e \"${GREEN}Step 1: Building WASM...${NC}\"\ncd \"$PROJECT_ROOT\"\ncargo build --target wasm32-unknown-unknown $RELEASE_FLAG\n\n# Determine the build directory\nif [[ \"$RELEASE_FLAG\" == \"--release\" ]]; then\n    BUILD_MODE=\"release\"\nelse\n    BUILD_MODE=\"debug\"\nfi\n\n# WASM file is in workspace target directory\nWORKSPACE_ROOT=\"$PROJECT_ROOT/../..\"\nWASM_PATH=\"$WORKSPACE_ROOT/target/wasm32-unknown-unknown/$BUILD_MODE/gpui_component_story_web.wasm\"\n\n# Check if WASM file exists\nif [[ ! -f \"$WASM_PATH\" ]]; then\n    echo -e \"${RED}Error: WASM file not found at: $WASM_PATH${NC}\"\n    exit 1\nfi\n\n# Step 2: Generate JavaScript bindings\necho -e \"${GREEN}Step 2: Generating JavaScript bindings...${NC}\"\nwasm-bindgen \"$WASM_PATH\" \\\n    --out-dir \"$PROJECT_ROOT/www/src/wasm\" \\\n    --target web \\\n    --no-typescript\n\necho -e \"${GREEN}✓ Build completed successfully!${NC}\"\necho -e \"${YELLOW}Next steps:${NC}\"\necho -e \"  cd www\"\necho -e \"  bun install\"\necho -e \"  bun run dev\"\n"
  },
  {
    "path": "crates/story-web/src/lib.rs",
    "content": "use gpui::{prelude::*, *};\nuse gpui_component::Root;\nuse gpui_component_assets::Assets;\nuse gpui_component_story::{Gallery, StoryRoot};\nuse wasm_bindgen::prelude::*;\n\n#[wasm_bindgen]\npub fn run() -> Result<(), JsValue> {\n    console_error_panic_hook::set_once();\n\n    // Initialize logging to browser console\n    console_log::init_with_level(log::Level::Info).expect(\"Failed to initialize logger\");\n\n    // Also initialize tracing for WASM\n    tracing_wasm::set_as_global_default();\n\n    #[cfg(target_family = \"wasm\")]\n    gpui_platform::web_init();\n    #[cfg(not(target_family = \"wasm\"))]\n    let app = gpui_platform::application();\n    #[cfg(target_family = \"wasm\")]\n    let app = gpui_platform::single_threaded_web();\n\n    app.with_assets(Assets::new(\n        \"https://longbridge.github.io/gpui-component/gallery/\",\n    ))\n    .run(|cx: &mut App| {\n        gpui_component_story::init(cx);\n\n        cx.open_window(WindowOptions::default(), |window, cx| {\n            let view = Gallery::view(None, window, cx);\n            let story_root = cx.new(|cx| StoryRoot::new(\"GPUI Component\", view, window, cx));\n            cx.new(|cx| Root::new(story_root, window, cx))\n        })\n        .expect(\"Failed to open window\");\n        cx.activate(true);\n    });\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/story-web/www/.gitignore",
    "content": "# Dependencies\nnode_modules/\nbun.lockb\n\n# Build outputs\ndist/\n*.local\n\n# WASM generated files\nsrc/wasm/\n!src/wasm/.gitkeep\n\n# Environment\n.env\n.env.local\n.env.*.local\n\n# Editor\n.vscode/\n.idea/\n*.swp\n*.swo\n*~\n\n# OS\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "crates/story-web/www/.prettierrc",
    "content": "{\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"tabWidth\": 2,\n  \"trailingComma\": \"es5\",\n  \"printWidth\": 100,\n  \"arrowParens\": \"avoid\"\n}\n"
  },
  {
    "path": "crates/story-web/www/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>GPUI Component Story Gallery</title>\n    <style>\n      * {\n        margin: 0;\n        padding: 0;\n        box-sizing: border-box;\n      }\n\n      body {\n        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n        overflow: hidden;\n        width: 100vw;\n        height: 100vh;\n      }\n\n      #app {\n        width: 100%;\n        height: 100%;\n        position: relative;\n      }\n\n      #canvas {\n        display: block;\n        width: 100%;\n        height: 100%;\n      }\n\n      #loading {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        transform: translate(-50%, -50%);\n        text-align: center;\n      }\n\n      .spinner {\n        border: 4px solid rgba(0, 0, 0, 0.1);\n        border-left-color: #000;\n        border-radius: 50%;\n        width: 40px;\n        height: 40px;\n        animation: spin 1s linear infinite;\n        margin: 0 auto 16px;\n      }\n\n      @keyframes spin {\n        to {\n          transform: rotate(360deg);\n        }\n      }\n\n      .error {\n        color: #d32f2f;\n        padding: 20px;\n        text-align: center;\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"app\">\n      <canvas id=\"canvas\"></canvas>\n      <div id=\"loading\">\n        <div class=\"spinner\"></div>\n        <p>Loading GPUI Component Story Gallery...</p>\n      </div>\n    </div>\n    <script type=\"module\" src=\"/src/main.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "crates/story-web/www/package.json",
    "content": "{\n  \"name\": \"gpui-component-story-web\",\n  \"version\": \"0.5.1\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\",\n    \"wasm\": \"cargo build --target wasm32-unknown-unknown --release && wasm-bindgen ../../../target/wasm32-unknown-unknown/release/gpui_component_story_web.wasm --out-dir ./src/wasm --target web --no-typescript\"\n  },\n  \"devDependencies\": {\n    \"vite\": \"^8\",\n    \"vite-plugin-static-copy\": \"^3.2.0\",\n    \"vite-plugin-wasm\": \"^3.3.0\"\n  }\n}\n"
  },
  {
    "path": "crates/story-web/www/src/main.js",
    "content": "async function init() {\n  const loadingEl = document.getElementById('loading');\n  const appEl = document.getElementById('app');\n\n  try {\n    // Import the WASM module\n    const wasm = await import('./wasm/gpui_component_story_web.js');\n    await wasm.default();\n\n    // Initialize the story gallery\n    await wasm.run();\n\n    // Hide loading indicator\n    if (loadingEl) {\n      loadingEl.remove();\n    }\n  } catch (error) {\n    console.error('Failed to initialize:', error);\n\n    // Show error message\n    if (loadingEl) {\n      loadingEl.innerHTML = `\n        <div class=\"error\">\n          <h2>Failed to load the application</h2>\n          <p>${error.message || error}</p>\n          <p style=\"margin-top: 10px; font-size: 14px;\">\n            Please check the console for more details.\n          </p>\n        </div>\n      `;\n    }\n  }\n}\n\ninit();\n"
  },
  {
    "path": "crates/story-web/www/vite.config.js",
    "content": "import { defineConfig } from 'vite';\nimport wasm from 'vite-plugin-wasm';\nimport { viteStaticCopy } from 'vite-plugin-static-copy';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport default defineConfig({\n  plugins: [\n    wasm(),\n    viteStaticCopy({\n      targets: [\n        {\n          src: path.resolve(__dirname, '../../assets/assets/icons'),\n          dest: 'assets',\n        },\n      ],\n    }),\n    {\n      name: 'serve-assets',\n      configureServer(server) {\n        server.middlewares.use('/gpui-component/gallery/assets', (req, res, next) => {\n          const assetsPath = path.resolve(__dirname, '../../assets/assets');\n          const filePath = path.join(assetsPath, req.url.replace('/assets', ''));\n\n          // Try to serve the file\n          import('fs').then(({ default: fs }) => {\n            if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {\n              res.setHeader('Access-Control-Allow-Origin', '*');\n              if (filePath.endsWith('.svg')) {\n                res.setHeader('Content-Type', 'image/svg+xml');\n              }\n              fs.createReadStream(filePath).pipe(res);\n            } else {\n              next();\n            }\n          });\n        });\n      },\n    },\n  ],\n  build: {\n    target: 'esnext',\n    minify: true,\n    sourcemap: false,\n    rollupOptions: {\n      output: {\n        manualChunks: undefined,\n      },\n    },\n  },\n  server: {\n    port: 3000,\n    open: true,\n    fs: {\n      strict: false,\n      allow: ['..'],\n    },\n    headers: {\n      'Cross-Origin-Embedder-Policy': 'require-corp',\n      'Cross-Origin-Opener-Policy': 'same-origin',\n    },\n  },\n  optimizeDeps: {\n    exclude: ['./src/wasm'],\n  },\n  base: '/gpui-component/gallery/',\n});\n"
  },
  {
    "path": "crates/ui/Cargo.toml",
    "content": "[package]\ndescription = \"UI components for building fantastic desktop application by using GPUI.\"\nkeywords = [\"desktop\", \"gpui\", \"shadcn\", \"ui\", \"uikit\"]\nlicense = \"Apache-2.0\"\nname = \"gpui-component\"\ndocumentation = \"https://docs.rs/gpui-component\"\nhomepage = \"https://longbridge.github.io/gpui-component\"\nrepository = \"https://github.com/longbridge/gpui-component\"\nreadme = \"../../README.md\"\nversion = \"0.5.1\"\npublish = true\nedition.workspace = true\n\n[lib]\ndoctest = false\n\n[features]\ndecimal = [\"dep:rust_decimal\"]\ninspector = [\"gpui_macros/inspector\", \"gpui/inspector\"]\n\n# For syntax highlighting in Markdown and CodeEditor.\ntree-sitter-languages = [\n    \"dep:tree-sitter-astro-next\",\n    \"dep:tree-sitter-bash\",\n    \"dep:tree-sitter-c\",\n    \"dep:tree-sitter-c-sharp\",\n    \"dep:tree-sitter-cmake\",\n    \"dep:tree-sitter-cpp\",\n    \"dep:tree-sitter-css\",\n    \"dep:tree-sitter-diff\",\n    \"dep:tree-sitter-elixir\",\n    \"dep:tree-sitter-embedded-template\",\n    \"dep:tree-sitter-go\",\n    \"dep:tree-sitter-graphql\",\n    \"dep:tree-sitter-html\",\n    \"dep:tree-sitter-java\",\n    \"dep:tree-sitter-javascript\",\n    \"dep:tree-sitter-jsdoc\",\n    \"dep:tree-sitter-kotlin-sg\",\n    \"dep:tree-sitter-lua\",\n    \"dep:tree-sitter-make\",\n    \"dep:tree-sitter-md\",\n    \"dep:tree-sitter-php\",\n    \"dep:tree-sitter-proto\",\n    \"dep:tree-sitter-python\",\n    \"dep:tree-sitter-ruby\",\n    \"dep:tree-sitter-rust\",\n    \"dep:tree-sitter-scala\",\n    \"dep:tree-sitter-sequel\",\n    \"dep:tree-sitter-swift\",\n    \"dep:tree-sitter-toml-ng\",\n    \"dep:tree-sitter-typescript\",\n    \"dep:tree-sitter-svelte-next\",\n    \"dep:tree-sitter-yaml\",\n    \"dep:tree-sitter-zig\",\n]\n\n[dependencies]\nanyhow.workspace = true\ngpui = { workspace = true }\ngpui_macros.workspace = true\ngpui-component-macros = { workspace = true }\nnotify.workspace = true\nropey.workspace = true\nrust-i18n.workspace = true\nschemars.workspace = true\nserde.workspace = true\nserde_json.workspace = true\nserde_repr.workspace = true\nsmallvec.workspace = true\nsum-tree.workspace = true\ntracing.workspace = true\nlog.workspace = true\n\nenum-iterator = \"2.1.0\"\nitertools = \"0.13.0\"\nonce_cell = \"1.19.0\"\npaste = \"1\"\nregex = \"1\"\nunicode-segmentation = \"1.12.0\"\nuuid = \"1.10\"\n\n# Async primitives (cross-platform, including WASM)\nasync-channel = \"2.3.1\"\nfutures = \"0.3\"\n\n# Chart\nnum-traits = \"0.2\"\nrust_decimal = { version = \"1.37.0\", optional = true }\n\n# Markdown Parser\nmarkdown = { version = \"1.0.0\", features = [\"serde\"] }\n\n# HTML Parser\nhtml5ever = \"0.27\"\nmarkup5ever_rcdom = \"0.3.0\"\n\n# Calendar\nchrono = \"0.4.38\"\n\n# Code Editor\naho-corasick = \"1.1.3\"\nlsp-types.workspace = true\n\n# Cross-platform time APIs (zero-cost on native, uses web APIs on WASM)\ninstant = { version = \"0.1\", features = [\"wasm-bindgen\"] }\n\n# Native-only dependencies (not available on WASM)\n[target.'cfg(not(target_family = \"wasm\"))'.dependencies]\nsmol.workspace = true\ntree-sitter = \"0.25.4\"\ntree-sitter-astro-next = { version=\"0.1.1\", optional = true }\ntree-sitter-bash = { version = \"0.23.3\", optional = true }\ntree-sitter-c = { version = \"0.24.1\", optional = true }\ntree-sitter-c-sharp = { version = \"0.23.1\", optional = true }\ntree-sitter-cmake = { version = \"0.7.1\", optional = true }\ntree-sitter-cpp = { version = \"0.23.4\", optional = true }\ntree-sitter-css = { version = \"0.23.2\", optional = true }\ntree-sitter-diff = { version = \"0.1.0\", optional = true }\ntree-sitter-elixir = { version = \"0.3\", optional = true }\ntree-sitter-embedded-template = { version = \"0.23.0\", optional = true }\ntree-sitter-go = { version = \"0.23.4\", optional = true }\ntree-sitter-graphql = { version = \"0.1.0\", optional = true }\ntree-sitter-html = { version = \"0.23.2\", optional = true }\ntree-sitter-java = { version = \"0.23.5\", optional = true }\ntree-sitter-javascript = { version = \"0.23.1\", optional = true }\ntree-sitter-jsdoc = { version = \"0.23.2\", optional = true }\ntree-sitter-json = \"0.24.8\"\ntree-sitter-kotlin-sg = { version = \"0.4.0\", optional = true }\ntree-sitter-lua = { version = \"0.4.1\", optional = true }\ntree-sitter-make = { version = \"1.1.1\", optional = true }\ntree-sitter-md = { version = \"0.5.1\", optional = true }\ntree-sitter-php = { version = \"0.24.2\", optional = true }\ntree-sitter-proto = { version = \"0.2.0\", optional = true }\ntree-sitter-python = { version = \"0.23.6\", optional = true }\ntree-sitter-ruby = { version = \"0.23.1\", optional = true }\ntree-sitter-rust = { version = \"0.24.0\", optional = true }\ntree-sitter-scala = { version = \"0.23.4\", optional = true }\ntree-sitter-sequel = { version = \"0.3.8\", optional = true }\ntree-sitter-svelte-next = { version = \"0.1.1\", optional = true }\ntree-sitter-swift = { version = \"0.7.0\", optional = true }\ntree-sitter-toml-ng = { version = \"0.7.0\", optional = true }\ntree-sitter-typescript = { version = \"0.23.2\", optional = true }\ntree-sitter-yaml = { version = \"0.7.1\", optional = true }\ntree-sitter-zig = { version = \"1.1.2\", optional = true }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncore-text = \"=21.0.0\"\n\n[dev-dependencies]\ngpui = { workspace = true, features = [\"test-support\"] }\nindoc = \"2\"\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "crates/ui/LICENSE-APACHE",
    "content": "Copyright 2024 - 2025 Longbridge <https://longbridge.com>\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n\n                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/ui/build.rs",
    "content": "use std::{fs, path::Path};\n\nfn main() {\n    // Tell Cargo to rerun this build script if any SVG file in assets/icons changes.\n    let icons_dir = Path::new(\"../assets/assets/icons\");\n\n    // Watch the icons directory itself.\n    println!(\"cargo:rerun-if-changed=../assets/assets/icons\");\n\n    // Watch each SVG file in the directory.\n    if let Ok(entries) = fs::read_dir(icons_dir) {\n        for entry in entries.flatten() {\n            let path = entry.path();\n            if path.extension().and_then(|s| s.to_str()) == Some(\"svg\") {\n                println!(\"cargo:rerun-if-changed={}\", path.display());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/locales/ui.yml",
    "content": "_version: 2\nCalendar:\n  week.0:\n    en: Su\n    zh-CN: 日\n    zh-HK: 日\n    it: Do\n  week.1:\n    en: Mo\n    zh-CN: 一\n    zh-HK: 一\n    it: Lu\n  week.2:\n    en: Tu\n    zh-CN: 二\n    zh-HK: 二\n    it: Ma\n  week.3:\n    en: We\n    zh-CN: 三\n    zh-HK: 三\n    it: Me\n  week.4:\n    en: Th\n    zh-CN: 四\n    zh-HK: 四\n    it: Gi\n  week.5:\n    en: Fr\n    zh-CN: 五\n    zh-HK: 五\n    it: Ve\n  week.6:\n    en: Sa\n    zh-CN: 六\n    zh-HK: 六\n    it: Sa\n  month.January:\n    en: January\n    zh-CN: 一月\n    zh-HK: 一月\n    it: Gennaio\n  month.February:\n    en: February\n    zh-CN: 二月\n    zh-HK: 二月\n    it: Febbraio\n  month.March:\n    en: March\n    zh-CN: 三月\n    zh-HK: 三月\n    it: Marzo\n  month.April:\n    en: April\n    zh-CN: 四月\n    zh-HK: 四月\n    it: Aprile\n  month.May:\n    en: May\n    zh-CN: 五月\n    zh-HK: 五月\n    it: Maggio\n  month.June:\n    en: June\n    zh-CN: 六月\n    zh-HK: 六月\n    it: Giugno\n  month.July:\n    en: July\n    zh-CN: 七月\n    zh-HK: 七月\n    it: Luglio\n  month.August:\n    en: August\n    zh-CN: 八月\n    zh-HK: 八月\n    it: Agosto\n  month.September:\n    en: September\n    zh-CN: 九月\n    zh-HK: 九月\n    it: Settembre\n  month.October:\n    en: October\n    zh-CN: 十月\n    zh-HK: 十月\n    it: Ottobre\n  month.November:\n    en: November\n    zh-CN: 十一月\n    zh-HK: 十一月\n    it: Novembre\n  month.December:\n    en: December\n    zh-CN: 十二月\n    zh-HK: 十二月\n    it: Dicembre\nDatePicker:\n  placeholder:\n    en: \"Select date\"\n    zh-CN: 选择日期\n    zh-HK: 選擇日期\n    it: \"Seleziona data\"\nSelect:\n  placeholder:\n    en: \"Please select\"\n    zh-CN: \"请选择\"\n    zh-HK: \"請選擇\"\n    it: Seleziona\nDock:\n  Unnamed:\n    en: Unnamed\n    zh-CN: 未命名\n    zh-HK: 未命名\n    it: \"Senza nome\"\n  Close:\n    en: Close\n    zh-CN: 关闭\n    zh-HK: 關閉\n    it: Chiudi\n  Zoom In:\n    en: Zoom In\n    zh-CN: 放大\n    zh-HK: 放大\n    it: Zoom In\n  Zoom Out:\n    en: Zoom Out\n    zh-CN: 缩小\n    zh-HK: 縮小\n    it: Zoom Out\n  Collapse:\n    en: Collapse\n    zh-CN: 隐藏\n    zh-HK: 隱藏\n    it: Nascondi\n  Expand:\n    en: Expand\n    zh-CN: 展开\n    zh-HK: 展開\n    it: Espandi\nColorPicker:\n  Palette:\n    en: Palette\n    zh-CN: 调色板\n    zh-HK: 調色板\n    it: Tavolozza\n  HSLA:\n    en: HSLA\n    zh-CN: HSLA\n    zh-HK: HSLA\n    it: HSLA\n  Hue:\n    en: Hue\n    zh-CN: 色相\n    zh-HK: 色相\n    it: Tonalità\n  Saturation:\n    en: Saturation\n    zh-CN: 饱和度\n    zh-HK: 飽和度\n    it: Saturazione\n  Lightness:\n    en: Lightness\n    zh-CN: 亮度\n    zh-HK: 亮度\n    it: Luminosità\n  Alpha:\n    en: Alpha\n    zh-CN: 透明度\n    zh-HK: 透明度\n    it: Alfa\nDialog:\n  ok:\n    en: OK\n    zh-CN: 确定\n    zh-HK: 確定\n    it: OK\n  cancel:\n    en: Cancel\n    zh-CN: 取消\n    zh-HK: 取消\n    it: Annulla\nList:\n  search_placeholder:\n    en: Search...\n    zh-CN: 搜索...\n    zh-HK: 搜索...\n    it: Ricerca...\nInput:\n  Replace:\n    en: Replace\n    zh-CN: 替换\n    zh-HK: 替換\n  Replace All:\n    en: Replace All\n    zh-CN: 全部替换\n    zh-HK: 全部替換\n  Cut:\n    en: Cut\n    zh-CN: 剪切\n    zh-HK: 剪切\n  Copy:\n    en: Copy\n    zh-CN: 复制\n    zh-HK: 複製\n  Paste:\n    en: Paste\n    zh-CN: 粘贴\n    zh-HK: 貼上\n  Select All:\n    en: Select All\n    zh-CN: 全选\n    zh-HK: 全選\n  Go to Definition:\n    en: Go to Definition\n    zh-CN: 跳转到定义\n    zh-HK: 跳轉到定義\n  Show Code Actions:\n    en: Show Code Actions\n    zh-CN: 显示代码操作\n    zh-HK: 顯示代碼操作\nSettings:\n  search_placeholder:\n    en: Search...\n    zh-CN: 搜索...\n    zh-HK: 搜索...\n    it: Ricerca...\n  Reset All:\n    en: Reset All\n    zh-CN: 重置全部\n    zh-HK: 重置全部\n    it: Resetta Tutto\nPagination:\n  previous:\n    en: Previous\n    zh-CN: 上一页\n    zh-HK: 上一頁\n  next:\n    en: Next\n    zh-CN: 下一页\n    zh-HK: 下一頁\n"
  },
  {
    "path": "crates/ui/src/accordion.rs",
    "content": "use std::{cell::RefCell, collections::HashSet, rc::Rc, sync::Arc};\n\nuse gpui::{\n    AnyElement, App, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,\n    SharedString, StatefulInteractiveElement as _, Styled, Window, div,\n    prelude::FluentBuilder as _, rems,\n};\n\nuse crate::{ActiveTheme as _, Icon, IconName, Sizable, Size, h_flex, v_flex};\n\n/// Accordion element.\n#[derive(IntoElement)]\npub struct Accordion {\n    id: ElementId,\n    multiple: bool,\n    size: Size,\n    bordered: bool,\n    disabled: bool,\n    children: Vec<AccordionItem>,\n    on_toggle_click: Option<Arc<dyn Fn(&[usize], &mut Window, &mut App) + Send + Sync>>,\n}\n\nimpl Accordion {\n    /// Create a new Accordion with the given ID.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            multiple: false,\n            size: Size::default(),\n            bordered: true,\n            children: Vec::new(),\n            disabled: false,\n            on_toggle_click: None,\n        }\n    }\n\n    /// Set whether multiple accordion items can be opened simultaneously, default: false\n    pub fn multiple(mut self, multiple: bool) -> Self {\n        self.multiple = multiple;\n        self\n    }\n\n    /// Set whether the accordion items have borders, default: true\n    pub fn bordered(mut self, bordered: bool) -> Self {\n        self.bordered = bordered;\n        self\n    }\n\n    /// Set whether the accordion is disabled, default: false\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    /// Adds an AccordionItem to the Accordion.\n    pub fn item<F>(mut self, child: F) -> Self\n    where\n        F: FnOnce(AccordionItem) -> AccordionItem,\n    {\n        let item = child(AccordionItem::new());\n        self.children.push(item);\n        self\n    }\n\n    /// Sets the on_toggle_click callback for the AccordionGroup.\n    ///\n    /// The first argument `Vec<usize>` is the indices of the open accordions.\n    pub fn on_toggle_click(\n        mut self,\n        on_toggle_click: impl Fn(&[usize], &mut Window, &mut App) + Send + Sync + 'static,\n    ) -> Self {\n        self.on_toggle_click = Some(Arc::new(on_toggle_click));\n        self\n    }\n}\n\nimpl Sizable for Accordion {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl RenderOnce for Accordion {\n    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {\n        let open_ixs = Rc::new(RefCell::new(HashSet::new()));\n        let is_multiple = self.multiple;\n\n        v_flex()\n            .id(self.id)\n            .size_full()\n            .when(self.bordered, |this| this.gap_y_2())\n            .children(\n                self.children\n                    .into_iter()\n                    .enumerate()\n                    .map(|(ix, accordion)| {\n                        if accordion.open {\n                            open_ixs.borrow_mut().insert(ix);\n                        }\n\n                        accordion\n                            .index(ix)\n                            .with_size(self.size)\n                            .bordered(self.bordered)\n                            .disabled(self.disabled)\n                            .on_toggle_click({\n                                let open_ixs = Rc::clone(&open_ixs);\n                                move |open, _, _| {\n                                    let mut open_ixs = open_ixs.borrow_mut();\n                                    if *open {\n                                        if !is_multiple {\n                                            open_ixs.clear();\n                                        }\n                                        open_ixs.insert(ix);\n                                    } else {\n                                        open_ixs.remove(&ix);\n                                    }\n                                }\n                            })\n                    }),\n            )\n            .when_some(\n                self.on_toggle_click.filter(|_| !self.disabled),\n                move |this, on_toggle_click| {\n                    let open_ixs = Rc::clone(&open_ixs);\n                    this.on_click(move |_, window, cx| {\n                        let open_ixs: Vec<usize> = open_ixs.borrow().iter().map(|&ix| ix).collect();\n\n                        on_toggle_click(&open_ixs, window, cx);\n                    })\n                },\n            )\n    }\n}\n\n/// An Accordion is a vertically stacked list of items, each of which can be expanded to reveal the content associated with it.\n#[derive(IntoElement)]\npub struct AccordionItem {\n    index: usize,\n    icon: Option<Icon>,\n    title: AnyElement,\n    children: Vec<AnyElement>,\n    open: bool,\n    size: Size,\n    bordered: bool,\n    disabled: bool,\n    on_toggle_click: Option<Arc<dyn Fn(&bool, &mut Window, &mut App)>>,\n}\n\nimpl AccordionItem {\n    /// Create a new AccordionItem.\n    pub fn new() -> Self {\n        Self {\n            index: 0,\n            icon: None,\n            title: SharedString::default().into_any_element(),\n            children: Vec::new(),\n            open: false,\n            disabled: false,\n            on_toggle_click: None,\n            size: Size::default(),\n            bordered: true,\n        }\n    }\n\n    /// Set the icon for the accordion item.\n    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {\n        self.icon = Some(icon.into());\n        self\n    }\n\n    /// Set the title for the accordion item.\n    pub fn title(mut self, title: impl IntoElement) -> Self {\n        self.title = title.into_any_element();\n        self\n    }\n\n    pub fn bordered(mut self, bordered: bool) -> Self {\n        self.bordered = bordered;\n        self\n    }\n\n    pub fn open(mut self, open: bool) -> Self {\n        self.open = open;\n        self\n    }\n\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    fn index(mut self, index: usize) -> Self {\n        self.index = index;\n        self\n    }\n\n    fn on_toggle_click(\n        mut self,\n        on_toggle_click: impl Fn(&bool, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_toggle_click = Some(Arc::new(on_toggle_click));\n        self\n    }\n}\n\nimpl ParentElement for AccordionItem {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Sizable for AccordionItem {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl RenderOnce for AccordionItem {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let text_size = match self.size {\n            Size::XSmall => rems(0.875),\n            Size::Small => rems(0.875),\n            _ => rems(1.0),\n        };\n\n        div().flex_1().child(\n            v_flex()\n                .w_full()\n                .bg(cx.theme().accordion)\n                .overflow_hidden()\n                .when(self.bordered, |this| {\n                    this.border_1()\n                        .rounded(cx.theme().radius)\n                        .border_color(cx.theme().border)\n                })\n                .text_size(text_size)\n                .child(\n                    h_flex()\n                        .id(self.index)\n                        .justify_between()\n                        .gap_3()\n                        .map(|this| match self.size {\n                            Size::XSmall => this.py_0().px_1p5(),\n                            Size::Small => this.py_0p5().px_2(),\n                            Size::Large => this.py_1p5().px_4(),\n                            _ => this.py_1().px_3(),\n                        })\n                        .when(self.open, |this| {\n                            this.when(self.bordered, |this| {\n                                this.text_color(cx.theme().foreground)\n                                    .border_b_1()\n                                    .border_color(cx.theme().border)\n                            })\n                        })\n                        .when(!self.bordered, |this| {\n                            this.border_b_1().border_color(cx.theme().border)\n                        })\n                        .child(\n                            h_flex()\n                                .items_center()\n                                .map(|this| match self.size {\n                                    Size::XSmall => this.gap_1(),\n                                    Size::Small => this.gap_1(),\n                                    _ => this.gap_2(),\n                                })\n                                .when_some(self.icon, |this, icon| {\n                                    this.child(\n                                        icon.with_size(self.size)\n                                            .text_color(cx.theme().muted_foreground),\n                                    )\n                                })\n                                .child(self.title),\n                        )\n                        .when(!self.disabled, |this| {\n                            this.hover(|this| this.bg(cx.theme().accordion_hover))\n                                .child(\n                                    Icon::new(if self.open {\n                                        IconName::ChevronUp\n                                    } else {\n                                        IconName::ChevronDown\n                                    })\n                                    .xsmall()\n                                    .text_color(cx.theme().muted_foreground),\n                                )\n                                .when_some(self.on_toggle_click, |this, on_toggle_click| {\n                                    this.on_click({\n                                        move |_, window, cx| {\n                                            on_toggle_click(&!self.open, window, cx);\n                                        }\n                                    })\n                                })\n                        }),\n                )\n                .when(self.open, |this| {\n                    this.child(\n                        div()\n                            .map(|this| match self.size {\n                                Size::XSmall => this.p_1p5(),\n                                Size::Small => this.p_2(),\n                                Size::Large => this.p_4(),\n                                _ => this.p_3(),\n                            })\n                            .children(self.children),\n                    )\n                }),\n        )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/actions.rs",
    "content": "use gpui::{actions, Action};\nuse serde::Deserialize;\n\n#[derive(Clone, Action, PartialEq, Eq, Deserialize)]\n#[action(namespace = ui, no_json)]\npub struct Confirm {\n    /// Is confirm with secondary.\n    pub secondary: bool,\n}\n\nactions!(ui, [Cancel, SelectUp, SelectDown, SelectLeft, SelectRight, SelectFirst, SelectLast, SelectPrevColumn, SelectNextColumn, SelectPageUp, SelectPageDown]);\n\n"
  },
  {
    "path": "crates/ui/src/alert.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{\n    App, ClickEvent, ElementId, Empty, Hsla, InteractiveElement, IntoElement, ParentElement as _,\n    RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, div,\n    prelude::FluentBuilder as _, px, rems, transparent_white,\n};\n\nuse crate::{\n    ActiveTheme as _, Colorize, Icon, IconName, Sizable, Size, StyledExt, h_flex,\n    text::{Text, TextViewStyle},\n};\n\n/// The variant of the [`Alert`].\n#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]\npub enum AlertVariant {\n    #[default]\n    Default,\n    Info,\n    Success,\n    Warning,\n    Error,\n}\n\nimpl AlertVariant {\n    fn fg(&self, cx: &App) -> Hsla {\n        match self {\n            Self::Default => cx.theme().foreground,\n            Self::Info => cx.theme().info,\n            Self::Success => cx.theme().success,\n            Self::Warning => cx.theme().warning,\n            Self::Error => cx.theme().danger,\n        }\n    }\n\n    fn bg(&self, cx: &App) -> Hsla {\n        match self {\n            Self::Default => cx.theme().background,\n            Self::Info => cx.theme().info.mix_oklab(transparent_white(), 0.04),\n            Self::Success => cx.theme().success.mix_oklab(transparent_white(), 0.04),\n            Self::Warning => cx.theme().warning.mix_oklab(transparent_white(), 0.04),\n            Self::Error => cx.theme().danger.mix_oklab(transparent_white(), 0.04),\n        }\n    }\n\n    fn border_color(&self, cx: &App) -> Hsla {\n        match self {\n            Self::Default => cx.theme().border,\n            Self::Info => cx.theme().info.mix_oklab(transparent_white(), 0.3),\n            Self::Success => cx.theme().success.mix_oklab(transparent_white(), 0.3),\n            Self::Warning => cx.theme().warning.mix_oklab(transparent_white(), 0.3),\n            Self::Error => cx.theme().danger.mix_oklab(transparent_white(), 0.3),\n        }\n    }\n}\n\n/// Alert used to display a message to the user.\n#[derive(IntoElement)]\npub struct Alert {\n    id: ElementId,\n    style: StyleRefinement,\n    variant: AlertVariant,\n    icon: Icon,\n    title: Option<SharedString>,\n    message: Text,\n    size: Size,\n    banner: bool,\n    on_close: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,\n    visible: bool,\n}\n\nimpl Alert {\n    /// Create a new alert with the given message.\n    pub fn new(id: impl Into<ElementId>, message: impl Into<Text>) -> Self {\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default(),\n            variant: AlertVariant::default(),\n            icon: Icon::new(IconName::Info),\n            title: None,\n            message: message.into(),\n            size: Size::default(),\n            banner: false,\n            visible: true,\n            on_close: None,\n        }\n    }\n\n    /// Create a new info [`AlertVariant::Info`] with the given message.\n    pub fn info(id: impl Into<ElementId>, message: impl Into<Text>) -> Self {\n        Self::new(id, message)\n            .with_variant(AlertVariant::Info)\n            .icon(IconName::Info)\n    }\n\n    /// Create a new [`AlertVariant::Success`] alert with the given message.\n    pub fn success(id: impl Into<ElementId>, message: impl Into<Text>) -> Self {\n        Self::new(id, message)\n            .with_variant(AlertVariant::Success)\n            .icon(IconName::CircleCheck)\n    }\n\n    /// Create a new [`AlertVariant::Warning`] alert with the given message.\n    pub fn warning(id: impl Into<ElementId>, message: impl Into<Text>) -> Self {\n        Self::new(id, message)\n            .with_variant(AlertVariant::Warning)\n            .icon(IconName::TriangleAlert)\n    }\n\n    /// Create a new [`AlertVariant::Error`] alert with the given message.\n    pub fn error(id: impl Into<ElementId>, message: impl Into<Text>) -> Self {\n        Self::new(id, message)\n            .with_variant(AlertVariant::Error)\n            .icon(IconName::CircleX)\n    }\n\n    /// Sets the [`AlertVariant`] of the alert.\n    pub fn with_variant(mut self, variant: AlertVariant) -> Self {\n        self.variant = variant;\n        self\n    }\n\n    /// Set the icon for the alert.\n    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {\n        self.icon = icon.into();\n        self\n    }\n\n    /// Set the title for the alert.\n    pub fn title(mut self, title: impl Into<SharedString>) -> Self {\n        self.title = Some(title.into());\n        self\n    }\n\n    /// Set alert as banner style.\n    ///\n    /// The `banner` style will make the alert take the full width of the container and not border and radius.\n    /// This mode will not display `title`.\n    pub fn banner(mut self) -> Self {\n        self.banner = true;\n        self\n    }\n\n    /// Set the visibility of the alert.\n    pub fn visible(mut self, visible: bool) -> Self {\n        self.visible = visible;\n        self\n    }\n\n    /// Set alert as closable, true will show Close icon.\n    pub fn on_close(\n        mut self,\n        on_close: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_close = Some(Rc::new(on_close));\n        self\n    }\n}\n\nimpl Sizable for Alert {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Styled for Alert {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Alert {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        if !self.visible {\n            return Empty.into_any_element();\n        }\n\n        let (radius, padding_x, padding_y, gap) = match self.size {\n            Size::XSmall => (cx.theme().radius, px(12.), px(6.), px(6.)),\n            Size::Small => (cx.theme().radius, px(12.), px(8.), px(6.)),\n            Size::Large => (cx.theme().radius_lg, px(20.), px(14.), px(12.)),\n            _ => (cx.theme().radius, px(16.), px(10.), px(12.)),\n        };\n\n        let bg = self.variant.bg(cx);\n        let fg = self.variant.fg(cx);\n        let border_color = self.variant.border_color(cx);\n\n        h_flex()\n            .id(self.id)\n            .w_full()\n            .text_color(fg)\n            .bg(bg)\n            .px(padding_x)\n            .py(padding_y)\n            .gap(gap)\n            .justify_between()\n            .text_sm()\n            .border_1()\n            .border_color(border_color)\n            .when(!self.banner, |this| this.rounded(radius).items_start())\n            .refine_style(&self.style)\n            .child(\n                div()\n                    .flex()\n                    .flex_1()\n                    .when(self.banner, |this| this.items_center())\n                    .overflow_hidden()\n                    .gap(gap)\n                    .child(\n                        div()\n                            .when(!self.banner, |this| this.mt(px(5.)))\n                            .child(self.icon),\n                    )\n                    .child(\n                        div()\n                            .flex_1()\n                            .overflow_hidden()\n                            .gap_3()\n                            .when(!self.banner, |this| {\n                                this.when_some(self.title, |this, title| {\n                                    this.child(\n                                        div().w_full().truncate().font_semibold().child(title),\n                                    )\n                                })\n                            })\n                            .child(\n                                self.message\n                                    .style(TextViewStyle::default().paragraph_gap(rems(0.2))),\n                            ),\n                    ),\n            )\n            .when_some(self.on_close, |this, on_close| {\n                this.child(\n                    div()\n                        .id(\"close\")\n                        .p_0p5()\n                        .rounded(cx.theme().radius)\n                        .hover(|this| this.bg(bg.opacity(0.8)))\n                        .active(|this| this.bg(bg.opacity(0.9)))\n                        .on_click(move |ev, window, cx| {\n                            on_close(ev, window, cx);\n                        })\n                        .child(\n                            Icon::new(IconName::Close)\n                                .with_size(self.size.max(Size::Medium))\n                                .flex_shrink_0(),\n                        ),\n                )\n            })\n            .into_any_element()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/anchored.rs",
    "content": "//! This is a fork of gpui's anchored element that adds support for offsetting\n//! https://github.com/zed-industries/zed/blob/b06f4088a3565c5e30663106ff79c1ced645d87a/crates/gpui/src/elements/anchored.rs\nuse gpui::{\n    AnyElement, App, Axis, Bounds, Display, Edges, Element, GlobalElementId, Half,\n    InspectorElementId, IntoElement, LayoutId, ParentElement, Pixels, Point, Position, Size, Style,\n    Window, point, px,\n};\nuse smallvec::SmallVec;\n\nuse crate::Anchor;\n\n/// The state that the anchored element element uses to track its children.\npub struct AnchoredState {\n    child_layout_ids: SmallVec<[LayoutId; 4]>,\n}\n\n/// An anchored element that can be used to display UI that\n/// will avoid overflowing the window bounds.\npub(crate) struct Anchored {\n    children: SmallVec<[AnyElement; 2]>,\n    anchor_corner: Anchor,\n    fit_mode: AnchoredFitMode,\n    anchor_position: Option<Point<Pixels>>,\n    position_mode: AnchoredPositionMode,\n    offset: Option<Point<Pixels>>,\n}\n\n/// anchored gives you an element that will avoid overflowing the window bounds.\n/// Its children should have no margin to avoid measurement issues.\npub(crate) fn anchored() -> Anchored {\n    Anchored {\n        children: SmallVec::new(),\n        anchor_corner: Anchor::TopLeft,\n        fit_mode: AnchoredFitMode::SwitchAnchor,\n        anchor_position: None,\n        position_mode: AnchoredPositionMode::Window,\n        offset: None,\n    }\n}\n\n#[allow(dead_code)]\nimpl Anchored {\n    /// Sets which corner of the anchored element should be anchored to the current position.\n    pub fn anchor(mut self, anchor: Anchor) -> Self {\n        self.anchor_corner = anchor;\n        self\n    }\n\n    /// Sets the position in window coordinates\n    /// (otherwise the location the anchored element is rendered is used)\n    pub fn position(mut self, anchor: Point<Pixels>) -> Self {\n        self.anchor_position = Some(anchor);\n        self\n    }\n\n    /// Offset the final position by this amount.\n    /// Useful when you want to anchor to an element but offset from it, such as in PopoverMenu.\n    pub fn offset(mut self, offset: Point<Pixels>) -> Self {\n        self.offset = Some(offset);\n        self\n    }\n\n    /// Sets the position mode for this anchored element. Local will have this\n    /// interpret its [`Anchored::position`] as relative to the parent element.\n    /// While Window will have it interpret the position as relative to the window.\n    pub fn position_mode(mut self, mode: AnchoredPositionMode) -> Self {\n        self.position_mode = mode;\n        self\n    }\n\n    /// Snap to window edge instead of switching anchor corner when an overflow would occur.\n    pub fn snap_to_window(mut self) -> Self {\n        self.fit_mode = AnchoredFitMode::SnapToWindow;\n        self\n    }\n\n    /// Snap to window edge and leave some margins.\n    pub fn snap_to_window_with_margin(mut self, edges: impl Into<Edges<Pixels>>) -> Self {\n        self.fit_mode = AnchoredFitMode::SnapToWindowWithMargin(edges.into());\n        self\n    }\n}\n\nimpl ParentElement for Anchored {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements)\n    }\n}\n\nimpl Element for Anchored {\n    type RequestLayoutState = AnchoredState;\n    type PrepaintState = ();\n\n    fn id(&self) -> Option<gpui::ElementId> {\n        None\n    }\n\n    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        _id: Option<&GlobalElementId>,\n        _inspector_id: Option<&InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (gpui::LayoutId, Self::RequestLayoutState) {\n        let child_layout_ids = self\n            .children\n            .iter_mut()\n            .map(|child| child.request_layout(window, cx))\n            .collect::<SmallVec<_>>();\n\n        let anchored_style = Style {\n            position: Position::Absolute,\n            display: Display::Flex,\n            ..Style::default()\n        };\n\n        let layout_id = window.request_layout(anchored_style, child_layout_ids.iter().copied(), cx);\n\n        (layout_id, AnchoredState { child_layout_ids })\n    }\n\n    fn prepaint(\n        &mut self,\n        _id: Option<&GlobalElementId>,\n        _inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        request_layout: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        if request_layout.child_layout_ids.is_empty() {\n            return;\n        }\n\n        let mut child_min = point(Pixels::MAX, Pixels::MAX);\n        let mut child_max = Point::default();\n        for child_layout_id in &request_layout.child_layout_ids {\n            let child_bounds = window.layout_bounds(*child_layout_id);\n            child_min = child_min.min(&child_bounds.origin);\n            child_max = child_max.max(&child_bounds.bottom_right());\n        }\n        let size: Size<Pixels> = (child_max - child_min).into();\n\n        let (origin, mut desired) = self.position_mode.get_position_and_bounds(\n            self.anchor_position,\n            self.anchor_corner,\n            size,\n            bounds,\n            self.offset,\n        );\n\n        let limits = Bounds {\n            origin: Point::default(),\n            size: window.viewport_size(),\n        };\n\n        if self.fit_mode == AnchoredFitMode::SwitchAnchor {\n            let mut anchor_corner = self.anchor_corner;\n\n            if desired.left() < limits.left() || desired.right() > limits.right() {\n                let switched = Bounds::from_corner_and_size(\n                    anchor_corner\n                        .other_side_corner_along(Axis::Horizontal)\n                        .into(),\n                    origin,\n                    size,\n                );\n                if !(switched.left() < limits.left() || switched.right() > limits.right()) {\n                    anchor_corner = anchor_corner.other_side_corner_along(Axis::Horizontal);\n                    desired = switched\n                }\n            }\n\n            if desired.top() < limits.top() || desired.bottom() > limits.bottom() {\n                let switched = Bounds::from_corner_and_size(\n                    anchor_corner.other_side_corner_along(Axis::Vertical).into(),\n                    origin,\n                    size,\n                );\n                if !(switched.top() < limits.top() || switched.bottom() > limits.bottom()) {\n                    desired = switched;\n                }\n            }\n        }\n\n        let client_inset = window.client_inset().unwrap_or(px(0.));\n        let edges = match self.fit_mode {\n            AnchoredFitMode::SnapToWindowWithMargin(edges) => edges,\n            _ => Edges::default(),\n        }\n        .map(|edge| *edge + client_inset);\n\n        // Snap the horizontal edges of the anchored element to the horizontal edges of the window if\n        // its horizontal bounds overflow, aligning to the left if it is wider than the limits.\n        if desired.right() > limits.right() {\n            desired.origin.x -= desired.right() - limits.right() + edges.right;\n        }\n        if desired.left() < limits.left() {\n            desired.origin.x = limits.origin.x + edges.left;\n        }\n\n        // Snap the vertical edges of the anchored element to the vertical edges of the window if\n        // its vertical bounds overflow, aligning to the top if it is taller than the limits.\n        if desired.bottom() > limits.bottom() {\n            desired.origin.y -= desired.bottom() - limits.bottom() + edges.bottom;\n        }\n        if desired.top() < limits.top() {\n            desired.origin.y = limits.origin.y + edges.top;\n        }\n\n        let offset = desired.origin - bounds.origin;\n        let offset = point(offset.x.round(), offset.y.round());\n\n        window.with_element_offset(offset, |window| {\n            for child in &mut self.children {\n                child.prepaint(window, cx);\n            }\n        })\n    }\n\n    fn paint(\n        &mut self,\n        _id: Option<&GlobalElementId>,\n        _inspector_id: Option<&InspectorElementId>,\n        _bounds: Bounds<Pixels>,\n        _request_layout: &mut Self::RequestLayoutState,\n        _prepaint: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        for child in &mut self.children {\n            child.paint(window, cx);\n        }\n    }\n}\n\nimpl IntoElement for Anchored {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\n/// Which algorithm to use when fitting the anchored element to be inside the window.\n#[allow(dead_code)]\n#[derive(Copy, Clone, PartialEq)]\npub enum AnchoredFitMode {\n    /// Snap the anchored element to the window edge.\n    SnapToWindow,\n    /// Snap to window edge and leave some margins.\n    SnapToWindowWithMargin(Edges<Pixels>),\n    /// Switch which corner anchor this anchored element is attached to.\n    SwitchAnchor,\n}\n\n/// Which algorithm to use when positioning the anchored element.\n#[allow(dead_code)]\n#[derive(Copy, Clone, PartialEq)]\npub enum AnchoredPositionMode {\n    /// Position the anchored element relative to the window.\n    Window,\n    /// Position the anchored element relative to its parent.\n    Local,\n}\n\nimpl AnchoredPositionMode {\n    fn get_position_and_bounds(\n        &self,\n        anchor_position: Option<Point<Pixels>>,\n        anchor_corner: Anchor,\n        size: Size<Pixels>,\n        bounds: Bounds<Pixels>,\n        offset: Option<Point<Pixels>>,\n    ) -> (Point<Pixels>, Bounds<Pixels>) {\n        let offset = offset.unwrap_or_default();\n\n        match self {\n            AnchoredPositionMode::Window => {\n                let anchor_position = anchor_position.unwrap_or(bounds.origin);\n                let bounds =\n                    Self::from_corner_and_size(anchor_corner, anchor_position + offset, size);\n                (anchor_position, bounds)\n            }\n            AnchoredPositionMode::Local => {\n                let anchor_position = anchor_position.unwrap_or_default();\n                let bounds = Self::from_corner_and_size(\n                    anchor_corner,\n                    bounds.origin + anchor_position + offset,\n                    size,\n                );\n                (anchor_position, bounds)\n            }\n        }\n    }\n\n    // Ref https://github.com/zed-industries/zed/blob/b06f4088a3565c5e30663106ff79c1ced645d87a/crates/gpui/src/geometry.rs#L863\n    fn from_corner_and_size(\n        anchor: Anchor,\n        origin: Point<Pixels>,\n        size: Size<Pixels>,\n    ) -> Bounds<Pixels> {\n        let origin = match anchor {\n            Anchor::TopLeft => origin,\n            Anchor::TopCenter => Point {\n                x: origin.x - size.width.half(),\n                y: origin.y,\n            },\n            Anchor::TopRight => Point {\n                x: origin.x - size.width,\n                y: origin.y,\n            },\n            Anchor::BottomLeft => Point {\n                x: origin.x,\n                y: origin.y - size.height,\n            },\n            Anchor::BottomCenter => Point {\n                x: origin.x - size.width.half(),\n                y: origin.y - size.height,\n            },\n            Anchor::BottomRight => Point {\n                x: origin.x - size.width,\n                y: origin.y - size.height,\n            },\n        };\n\n        Bounds { origin, size }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/animation.rs",
    "content": "/// A cubic bezier function like CSS `cubic-bezier`.\n///\n/// Builder:\n///\n/// https://cubic-bezier.com\npub fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> impl Fn(f32) -> f32 {\n    move |t: f32| {\n        let one_t = 1.0 - t;\n        let one_t2 = one_t * one_t;\n        let t2 = t * t;\n        let t3 = t2 * t;\n\n        // The Bezier curve function for x and y, where x0 = 0, y0 = 0, x3 = 1, y3 = 1\n        let _x = 3.0 * x1 * one_t2 * t + 3.0 * x2 * one_t * t2 + t3;\n        let y = 3.0 * y1 * one_t2 * t + 3.0 * y2 * one_t * t2 + t3;\n\n        y\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/async_util.rs",
    "content": "//! Cross-platform async utilities for both native and WASM targets.\n\n// For native targets, re-export smol's primitives\n#[cfg(not(target_family = \"wasm\"))]\npub use smol::channel::{Receiver, Sender, unbounded};\n\n// For WASM targets, use async-channel\n#[cfg(target_family = \"wasm\")]\npub use async_channel::{Receiver, Sender, unbounded};\n"
  },
  {
    "path": "crates/ui/src/avatar/avatar.rs",
    "content": "use gpui::{\n    App, Div, Hsla, ImageSource, InteractiveElement, Interactivity, IntoElement,\n    ParentElement as _, RenderOnce, SharedString, StyleRefinement, Styled, Window, div, img,\n    prelude::FluentBuilder,\n};\n\nuse crate::{\n    ActiveTheme, Colorize, Icon, IconName, Sizable, Size, StyledExt,\n    avatar::{AvatarSized as _, avatar_size},\n};\n\n/// User avatar element.\n///\n/// We can use [`Sizable`] trait to set the size of the avatar (see also: [`avatar_size`] about the size in pixels).\n#[derive(IntoElement)]\npub struct Avatar {\n    base: Div,\n    style: StyleRefinement,\n    src: Option<ImageSource>,\n    name: Option<SharedString>,\n    short_name: SharedString,\n    placeholder: Icon,\n    size: Size,\n}\n\nimpl Avatar {\n    pub fn new() -> Self {\n        Self {\n            base: div(),\n            style: StyleRefinement::default(),\n            src: None,\n            name: None,\n            short_name: SharedString::default(),\n            placeholder: Icon::new(IconName::User),\n            size: Size::Medium,\n        }\n    }\n\n    /// Set to use image source for the avatar.\n    pub fn src(mut self, source: impl Into<ImageSource>) -> Self {\n        self.src = Some(source.into());\n        self\n    }\n\n    /// Set name of the avatar user, if `src` is none, will use this name as placeholder.\n    pub fn name(mut self, name: impl Into<SharedString>) -> Self {\n        let name: SharedString = name.into();\n        let short: SharedString = extract_text_initials(&name).into();\n\n        self.name = Some(name);\n        self.short_name = short;\n        self\n    }\n\n    /// Set placeholder icon, default: [`IconName::User`]\n    pub fn placeholder(mut self, icon: impl Into<Icon>) -> Self {\n        self.placeholder = icon.into();\n        self\n    }\n}\n\nimpl Sizable for Avatar {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Styled for Avatar {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl InteractiveElement for Avatar {\n    fn interactivity(&mut self) -> &mut Interactivity {\n        self.base.interactivity()\n    }\n}\n\nimpl RenderOnce for Avatar {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let corner_radii = self.style.corner_radii.clone();\n        let mut inner_style = StyleRefinement::default();\n        inner_style.corner_radii = corner_radii;\n\n        const COLOR_COUNT: u64 = 360 / 15;\n        fn default_color(ix: u64, cx: &mut App) -> Hsla {\n            let h = (ix * 15).clamp(0, 360) as f32;\n            cx.theme().blue.hue(h / 360.0)\n        }\n\n        const BG_OPACITY: f32 = 0.2;\n\n        self.base\n            .avatar_size(self.size)\n            .flex()\n            .items_center()\n            .justify_center()\n            .flex_shrink_0()\n            .rounded_full()\n            .overflow_hidden()\n            .bg(cx.theme().secondary)\n            .text_color(cx.theme().background)\n            .border_1()\n            .border_color(cx.theme().border)\n            .when(self.name.is_none() && self.src.is_none(), |this| {\n                this.text_size(avatar_size(self.size) * 0.6)\n                    .child(self.placeholder)\n            })\n            .map(|this| match self.src {\n                None => this.when(self.name.is_some(), |this| {\n                    let color_ix = gpui::hash(&self.short_name) % COLOR_COUNT;\n                    let color = default_color(color_ix, cx);\n\n                    this.bg(color.opacity(BG_OPACITY))\n                        .text_color(color)\n                        .child(div().avatar_text_size(self.size).child(self.short_name))\n                }),\n                Some(src) => this.child(\n                    img(src)\n                        .avatar_size(self.size)\n                        .rounded_full()\n                        .refine_style(&inner_style),\n                ),\n            })\n            .refine_style(&self.style)\n    }\n}\n\nfn extract_text_initials(text: &str) -> String {\n    let mut result = text\n        .split(\" \")\n        .flat_map(|word| word.chars().next().map(|c| c.to_string()))\n        .take(2)\n        .collect::<Vec<String>>()\n        .join(\"\");\n\n    if result.len() == 1 {\n        result = text.chars().take(2).collect::<String>();\n    }\n\n    result.to_uppercase()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_avatar_text_initials() {\n        assert_eq!(extract_text_initials(&\"Jason Lee\"), \"JL\".to_string());\n        assert_eq!(extract_text_initials(&\"Foo Bar Dar\"), \"FB\".to_string());\n        assert_eq!(extract_text_initials(&\"huacnlee\"), \"HU\".to_string());\n    }\n\n    #[gpui::test]\n    fn test_avatar_builder(_cx: &mut gpui::TestAppContext) {\n        let avatar = Avatar::new()\n            .name(\"Jason Lee\")\n            .placeholder(Icon::new(IconName::User))\n            .large();\n\n        assert_eq!(avatar.name, Some(SharedString::from(\"Jason Lee\")));\n        assert_eq!(avatar.short_name, SharedString::from(\"JL\"));\n        assert_eq!(avatar.size, Size::Large);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/avatar/avatar_group.rs",
    "content": "use gpui::{\n    div, prelude::FluentBuilder as _, Div, InteractiveElement, Interactivity, IntoElement,\n    ParentElement as _, RenderOnce, StyleRefinement, Styled,\n};\n\nuse crate::{avatar::Avatar, ActiveTheme, Sizable, Size, StyledExt as _};\n\n/// A grouped avatars to display in a compact layout.\n#[derive(IntoElement)]\npub struct AvatarGroup {\n    base: Div,\n    style: StyleRefinement,\n    avatars: Vec<Avatar>,\n    size: Size,\n    limit: usize,\n    ellipsis: bool,\n}\n\nimpl AvatarGroup {\n    /// Create a new AvatarGroup.\n    pub fn new() -> Self {\n        Self {\n            base: div(),\n            style: StyleRefinement::default(),\n            avatars: Vec::new(),\n            size: Size::default(),\n            limit: 3,\n            ellipsis: false,\n        }\n    }\n\n    /// Add a child avatar to the group.\n    pub fn child(mut self, avatar: Avatar) -> Self {\n        self.avatars.push(avatar);\n        self\n    }\n\n    /// Add multiple child avatars to the group.\n    pub fn children(mut self, avatars: impl IntoIterator<Item = Avatar>) -> Self {\n        self.avatars.extend(avatars);\n        self\n    }\n\n    /// Set the maximum number of avatars to display before showing a \"more\" avatar.\n    pub fn limit(mut self, limit: usize) -> Self {\n        self.limit = limit;\n        self\n    }\n\n    /// Set whether to show an ellipsis when the limit is reached, default: false\n    pub fn ellipsis(mut self) -> Self {\n        self.ellipsis = true;\n        self\n    }\n}\n\nimpl Sizable for AvatarGroup {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Styled for AvatarGroup {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl InteractiveElement for AvatarGroup {\n    fn interactivity(&mut self) -> &mut Interactivity {\n        self.base.interactivity()\n    }\n}\n\nimpl RenderOnce for AvatarGroup {\n    fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {\n        let item_ml = -super::avatar_size(self.size) * 0.3;\n        let avatars_len = self.avatars.len();\n\n        self.base\n            .h_flex()\n            .flex_row_reverse()\n            .refine_style(&self.style)\n            .children(if self.ellipsis && avatars_len > self.limit {\n                Some(\n                    Avatar::new()\n                        .name(\"⋯\")\n                        .bg(cx.theme().secondary)\n                        .text_color(cx.theme().muted_foreground)\n                        .with_size(self.size)\n                        .ml_1(),\n                )\n            } else {\n                None\n            })\n            .children(\n                self.avatars\n                    .into_iter()\n                    .take(self.limit)\n                    .enumerate()\n                    .rev()\n                    .map(|(ix, item)| {\n                        item.with_size(self.size)\n                            .when(ix > 0, |this| this.ml(item_ml))\n                    }),\n            )\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[gpui::test]\n    fn test_avatar_group_builder(_cx: &mut gpui::TestAppContext) {\n        let group = AvatarGroup::new()\n            .child(Avatar::new().name(\"Alice\"))\n            .child(Avatar::new().name(\"Bob\"))\n            .child(Avatar::new().name(\"Charlie\"))\n            .child(Avatar::new().name(\"David\"))\n            .large()\n            .limit(3)\n            .ellipsis();\n\n        assert_eq!(group.avatars.len(), 4);\n        assert_eq!(group.size, Size::Large);\n        assert_eq!(group.limit, 3);\n        assert!(group.ellipsis);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/avatar/mod.rs",
    "content": "mod avatar;\nmod avatar_group;\n\npub use avatar::*;\npub use avatar_group::*;\n\nuse crate::{Icon, Size, StyledExt as _};\nuse gpui::{Div, Img, IntoElement, Pixels, Styled, px, rems};\n\n/// Returns the size of the avatar based on the given [`Size`].\npub(super) fn avatar_size(size: Size) -> Pixels {\n    match size {\n        Size::Large => px(80.),\n        Size::Medium => px(48.),\n        Size::Small => px(24.),\n        Size::XSmall => px(16.),\n        Size::Size(size) => size,\n    }\n}\n\n/// Extension for add `avatar_size` method to `IntoElement` to apply avatar size to element.\npub(super) trait AvatarSized: IntoElement + Styled {\n    fn avatar_size(self, size: Size) -> Self {\n        self.size(avatar_size(size))\n    }\n\n    fn avatar_text_size(self, size: Size) -> Self {\n        match size {\n            Size::Large => self.text_3xl().font_semibold(),\n            Size::Medium => self.text_sm(),\n            Size::Small => self.text_xs(),\n            Size::XSmall => self.text_size(rems(0.65)),\n            Size::Size(size) => self.size(size * 0.5),\n        }\n    }\n}\nimpl AvatarSized for Div {}\nimpl AvatarSized for Icon {}\nimpl AvatarSized for Img {}\n"
  },
  {
    "path": "crates/ui/src/badge.rs",
    "content": "use gpui::{\n    div, prelude::FluentBuilder, px, relative, AnyElement, App, Hsla, IntoElement, ParentElement,\n    RenderOnce, StyleRefinement, Styled, Window,\n};\n\nuse crate::{h_flex, white, ActiveTheme, Icon, Sizable, Size, StyledExt};\n\n#[derive(Default, Clone)]\nenum BadgeVariant {\n    #[default]\n    Number,\n    Dot,\n    Icon(Box<Icon>),\n}\n\n#[allow(unused)]\nimpl BadgeVariant {\n    #[inline]\n    fn is_icon(&self) -> bool {\n        matches!(self, BadgeVariant::Icon(_))\n    }\n\n    #[inline]\n    fn is_number(&self) -> bool {\n        matches!(self, BadgeVariant::Number)\n    }\n}\n\n/// A badge for displaying a count, dot, or icon on an element.\n#[derive(IntoElement)]\npub struct Badge {\n    style: StyleRefinement,\n    count: usize,\n    max: usize,\n    variant: BadgeVariant,\n    children: Vec<AnyElement>,\n    color: Option<Hsla>,\n    size: Size,\n}\n\nimpl Badge {\n    /// Create a new badge.\n    pub fn new() -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            count: 0,\n            max: 99,\n            variant: Default::default(),\n            color: None,\n            children: Vec::new(),\n            size: Size::default(),\n        }\n    }\n\n    /// Set to use [`BadgeVariant::Dot`] to show a dot.\n    pub fn dot(mut self) -> Self {\n        self.variant = BadgeVariant::Dot;\n        self\n    }\n\n    /// Set to use [`BadgeVariant::Number`] to show a count.\n    ///\n    /// If count is 0, the badge will be hidden.\n    pub fn count(mut self, count: usize) -> Self {\n        self.count = count;\n        self\n    }\n\n    /// Set to use [`BadgeVariant::Icon`] to show an icon.\n    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {\n        self.variant = BadgeVariant::Icon(Box::new(icon.into()));\n        self\n    }\n\n    /// Set the maximum count to show (Only if [`BadgeVariant::Number`] is used).\n    pub fn max(mut self, max: usize) -> Self {\n        self.max = max;\n        self\n    }\n\n    /// Set the color (background) of the badge.\n    pub fn color(mut self, color: impl Into<Hsla>) -> Self {\n        self.color = Some(color.into());\n        self\n    }\n}\n\nimpl ParentElement for Badge {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Sizable for Badge {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl RenderOnce for Badge {\n    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let visible = match self.variant {\n            BadgeVariant::Number => self.count > 0,\n            BadgeVariant::Dot | BadgeVariant::Icon(_) => true,\n        };\n\n        let (size, text_size) = match self.size {\n            Size::Large => (px(24.), px(14.)),\n            Size::Medium | Size::Size(_) => (px(16.), px(10.)),\n            Size::Small | Size::XSmall => (px(10.), px(8.)),\n        };\n\n        div()\n            .relative()\n            .refine_style(&self.style)\n            .children(self.children)\n            .when(visible, |this| {\n                this.child(\n                    h_flex()\n                        .absolute()\n                        .justify_center()\n                        .items_center()\n                        .rounded_full()\n                        .bg(self.color.unwrap_or(cx.theme().red))\n                        .text_color(white())\n                        .text_size(text_size)\n                        .map(|this| match self.variant {\n                            BadgeVariant::Dot => this.top_0().right_0().size(px(6.)),\n                            BadgeVariant::Number => {\n                                let count = if self.count > self.max {\n                                    format!(\"{}+\", self.max)\n                                } else {\n                                    self.count.to_string()\n                                };\n\n                                let (top, left) = match self.size {\n                                    Size::Large => (px(2.), -px(count.len() as f32)),\n                                    Size::Medium | Size::Size(_) => {\n                                        (-px(3.), -px(3.) * count.len())\n                                    }\n                                    Size::Small | Size::XSmall => (-px(4.), -px(4.) * count.len()),\n                                };\n\n                                this.top(top)\n                                    .right(left)\n                                    .py_0p5()\n                                    .px_0p5()\n                                    .min_w_3p5()\n                                    .text_size(px(10.))\n                                    .line_height(relative(1.))\n                                    .child(count)\n                            }\n                            BadgeVariant::Icon(icon) => this\n                                .right_0()\n                                .bottom_0()\n                                .size(size)\n                                .border_1()\n                                .border_color(cx.theme().background)\n                                .child(*icon),\n                        }),\n                )\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/breadcrumb.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{\n    div, prelude::FluentBuilder as _, App, ClickEvent, ElementId, InteractiveElement as _,\n    IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement,\n    StyleRefinement, Styled, Window,\n};\n\nuse crate::{h_flex, ActiveTheme, Icon, IconName, StyledExt};\n\n/// A breadcrumb navigation element.\n#[derive(IntoElement)]\npub struct Breadcrumb {\n    style: StyleRefinement,\n    items: Vec<BreadcrumbItem>,\n}\n\n/// Item for the [`Breadcrumb`].\n#[derive(IntoElement)]\npub struct BreadcrumbItem {\n    id: ElementId,\n    style: StyleRefinement,\n    label: SharedString,\n    on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,\n    disabled: bool,\n    is_last: bool,\n}\n\nimpl BreadcrumbItem {\n    /// Create a new BreadcrumbItem with the given id and label.\n    pub fn new(label: impl Into<SharedString>) -> Self {\n        Self {\n            id: ElementId::Integer(0),\n            style: StyleRefinement::default(),\n            label: label.into(),\n            on_click: None,\n            disabled: false,\n            is_last: false,\n        }\n    }\n\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    pub fn on_click(\n        mut self,\n        on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_click = Some(Rc::new(on_click));\n        self\n    }\n\n    fn id(mut self, id: impl Into<ElementId>) -> Self {\n        self.id = id.into();\n        self\n    }\n\n    /// For internal use only.\n    fn is_last(mut self, is_last: bool) -> Self {\n        self.is_last = is_last;\n        self\n    }\n}\n\nimpl Styled for BreadcrumbItem {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl From<&'static str> for BreadcrumbItem {\n    fn from(value: &'static str) -> Self {\n        Self::new(value)\n    }\n}\n\nimpl From<String> for BreadcrumbItem {\n    fn from(value: String) -> Self {\n        Self::new(value)\n    }\n}\n\nimpl From<SharedString> for BreadcrumbItem {\n    fn from(value: SharedString) -> Self {\n        Self::new(value)\n    }\n}\n\nimpl RenderOnce for BreadcrumbItem {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        div()\n            .id(self.id)\n            .child(self.label)\n            .text_color(cx.theme().muted_foreground)\n            .when(self.is_last, |this| this.text_color(cx.theme().foreground))\n            .when(self.disabled, |this| {\n                this.text_color(cx.theme().muted_foreground)\n            })\n            .refine_style(&self.style)\n            .when(!self.disabled, |this| {\n                this.when_some(self.on_click, |this, on_click| {\n                    this.cursor_pointer().on_click(move |event, window, cx| {\n                        on_click(event, window, cx);\n                    })\n                })\n            })\n    }\n}\n\nimpl Breadcrumb {\n    /// Create a new breadcrumb.\n    pub fn new() -> Self {\n        Self {\n            items: Vec::new(),\n            style: StyleRefinement::default(),\n        }\n    }\n\n    /// Add an [`BreadcrumbItem`] to the breadcrumb.\n    pub fn child(mut self, item: impl Into<BreadcrumbItem>) -> Self {\n        self.items.push(item.into());\n        self\n    }\n\n    /// Add multiple [`BreadcrumbItem`] items to the breadcrumb.\n    pub fn children(mut self, items: impl IntoIterator<Item = impl Into<BreadcrumbItem>>) -> Self {\n        self.items.extend(items.into_iter().map(Into::into));\n        self\n    }\n}\n\n#[derive(IntoElement)]\nstruct BreadcrumbSeparator;\nimpl RenderOnce for BreadcrumbSeparator {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        Icon::new(IconName::ChevronRight)\n            .text_color(cx.theme().muted_foreground)\n            .size_3p5()\n            .into_any_element()\n    }\n}\n\nimpl Styled for Breadcrumb {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Breadcrumb {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let items_count = self.items.len();\n\n        let mut children = vec![];\n        for (ix, item) in self.items.into_iter().enumerate() {\n            let is_last = ix == items_count - 1;\n\n            let item = item.id(ix);\n            children.push(item.is_last(is_last).into_any_element());\n            if !is_last {\n                children.push(BreadcrumbSeparator.into_any_element());\n            }\n        }\n\n        h_flex()\n            .gap_1p5()\n            .text_sm()\n            .text_color(cx.theme().muted_foreground)\n            .refine_style(&self.style)\n            .children(children)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/button/button.rs",
    "content": "use std::rc::Rc;\n\nuse crate::{\n    ActiveTheme, Colorize as _, Disableable, FocusableExt as _, Icon, IconName, Selectable,\n    Sizable, Size, StyleSized, StyledExt, button::ButtonIcon, h_flex, tooltip::Tooltip,\n};\nuse gpui::{\n    Action, AnyElement, App, ClickEvent, Corners, Div, Edges, ElementId, Hsla, InteractiveElement,\n    Interactivity, IntoElement, MouseButton, ParentElement, Pixels, RenderOnce, SharedString,\n    Stateful, StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div,\n    prelude::FluentBuilder as _, px, relative, transparent_white,\n};\n\n#[derive(Default, Clone, Copy)]\npub enum ButtonRounded {\n    None,\n    Small,\n    #[default]\n    Medium,\n    Large,\n    Size(Pixels),\n}\n\nimpl From<Pixels> for ButtonRounded {\n    fn from(px: Pixels) -> Self {\n        ButtonRounded::Size(px)\n    }\n}\n\n#[derive(Clone, Copy, PartialEq, Eq, Debug)]\npub struct ButtonCustomVariant {\n    color: Hsla,\n    foreground: Hsla,\n    shadow: bool,\n    hover: Hsla,\n    active: Hsla,\n}\n\npub trait ButtonVariants: Sized {\n    fn with_variant(self, variant: ButtonVariant) -> Self;\n\n    /// With the primary style for the Button.\n    fn primary(self) -> Self {\n        self.with_variant(ButtonVariant::Primary)\n    }\n\n    /// With the secondary style for the Button.\n    fn secondary(self) -> Self {\n        self.with_variant(ButtonVariant::Secondary)\n    }\n\n    /// With the danger style for the Button.\n    fn danger(self) -> Self {\n        self.with_variant(ButtonVariant::Danger)\n    }\n\n    /// With the warning style for the Button.\n    fn warning(self) -> Self {\n        self.with_variant(ButtonVariant::Warning)\n    }\n\n    /// With the success style for the Button.\n    fn success(self) -> Self {\n        self.with_variant(ButtonVariant::Success)\n    }\n\n    /// With the info style for the Button.\n    fn info(self) -> Self {\n        self.with_variant(ButtonVariant::Info)\n    }\n\n    /// With the ghost style for the Button.\n    fn ghost(self) -> Self {\n        self.with_variant(ButtonVariant::Ghost)\n    }\n\n    /// With the link style for the Button.\n    fn link(self) -> Self {\n        self.with_variant(ButtonVariant::Link)\n    }\n\n    /// With the text style for the Button, it will no padding look like a normal text.\n    fn text(self) -> Self {\n        self.with_variant(ButtonVariant::Text)\n    }\n\n    /// With the custom style for the Button.\n    fn custom(self, style: ButtonCustomVariant) -> Self {\n        self.with_variant(ButtonVariant::Custom(style))\n    }\n}\n\nimpl ButtonCustomVariant {\n    pub fn new(cx: &App) -> Self {\n        Self {\n            color: cx.theme().transparent,\n            foreground: cx.theme().foreground,\n            hover: cx.theme().transparent,\n            active: cx.theme().transparent,\n            shadow: false,\n        }\n    }\n\n    /// Set background color, default is transparent.\n    pub fn color(mut self, color: Hsla) -> Self {\n        self.color = color;\n        self\n    }\n\n    /// Set foreground color, default is theme foreground.\n    pub fn foreground(mut self, color: Hsla) -> Self {\n        self.foreground = color;\n        self\n    }\n\n    /// Set hover background color, default is transparent.\n    pub fn hover(mut self, color: Hsla) -> Self {\n        self.hover = color;\n        self\n    }\n\n    /// Set active background color, default is transparent.\n    pub fn active(mut self, color: Hsla) -> Self {\n        self.active = color;\n        self\n    }\n\n    /// Set shadow, default is false.\n    pub fn shadow(mut self, shadow: bool) -> Self {\n        self.shadow = shadow;\n        self\n    }\n}\n\n/// The variant of the Button.\n#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]\npub enum ButtonVariant {\n    #[default]\n    Default,\n    Primary,\n    Secondary,\n    Danger,\n    Info,\n    Success,\n    Warning,\n    Ghost,\n    Link,\n    Text,\n    Custom(ButtonCustomVariant),\n}\n\nimpl ButtonVariant {\n    #[inline]\n    pub fn is_link(&self) -> bool {\n        matches!(self, Self::Link)\n    }\n\n    #[inline]\n    pub fn is_text(&self) -> bool {\n        matches!(self, Self::Text)\n    }\n\n    #[inline]\n    pub fn is_ghost(&self) -> bool {\n        matches!(self, Self::Ghost)\n    }\n\n    #[inline]\n    fn no_padding(&self) -> bool {\n        self.is_link() || self.is_text()\n    }\n\n    #[inline]\n    fn is_default(&self) -> bool {\n        matches!(self, Self::Default)\n    }\n}\n\n/// A Button element.\n#[derive(IntoElement)]\npub struct Button {\n    id: ElementId,\n    base: Stateful<Div>,\n    style: StyleRefinement,\n    icon: Option<ButtonIcon>,\n    label: Option<SharedString>,\n    children: Vec<AnyElement>,\n    disabled: bool,\n    pub(crate) selected: bool,\n    variant: ButtonVariant,\n    rounded: ButtonRounded,\n    outline: bool,\n    border_corners: Corners<bool>,\n    border_edges: Edges<bool>,\n    dropdown_caret: bool,\n    size: Size,\n    compact: bool,\n    tooltip: Option<(\n        SharedString,\n        Option<(Rc<Box<dyn Action>>, Option<SharedString>)>,\n    )>,\n    on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,\n    on_hover: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,\n    loading: bool,\n    loading_icon: Option<Icon>,\n\n    tab_index: isize,\n    tab_stop: bool,\n}\n\nimpl From<Button> for AnyElement {\n    fn from(button: Button) -> Self {\n        button.into_any_element()\n    }\n}\n\nimpl Button {\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        let id = id.into();\n\n        Self {\n            id: id.clone(),\n            // ID must be set after div is created;\n            // `dropdown_menu` uses this id to create the popup menu.\n            base: div().flex_shrink_0().id(id),\n            style: StyleRefinement::default(),\n            icon: None,\n            label: None,\n            disabled: false,\n            selected: false,\n            variant: ButtonVariant::default(),\n            rounded: ButtonRounded::Medium,\n            border_corners: Corners::all(true),\n            border_edges: Edges::all(true),\n            size: Size::Medium,\n            tooltip: None,\n            on_click: None,\n            on_hover: None,\n            loading: false,\n            compact: false,\n            outline: false,\n            children: Vec::new(),\n            loading_icon: None,\n            dropdown_caret: false,\n            tab_index: 0,\n            tab_stop: true,\n        }\n    }\n\n    /// Set the outline style of the Button.\n    pub fn outline(mut self) -> Self {\n        self.outline = true;\n        self\n    }\n\n    /// Set the border radius of the Button.\n    pub fn rounded(mut self, rounded: impl Into<ButtonRounded>) -> Self {\n        self.rounded = rounded.into();\n        self\n    }\n\n    /// Set the border corners side of the Button.\n    pub(crate) fn border_corners(mut self, corners: impl Into<Corners<bool>>) -> Self {\n        self.border_corners = corners.into();\n        self\n    }\n\n    /// Set the border edges of the Button.\n    pub(crate) fn border_edges(mut self, edges: impl Into<Edges<bool>>) -> Self {\n        self.border_edges = edges.into();\n        self\n    }\n\n    /// Set label to the Button, if no label is set, the button will be in Icon Button mode.\n    pub fn label(mut self, label: impl Into<SharedString>) -> Self {\n        self.label = Some(label.into());\n        self\n    }\n\n    /// Set the icon of the button, if the Button have no label, the button well in Icon Button mode.\n    pub fn icon(mut self, icon: impl Into<ButtonIcon>) -> Self {\n        self.icon = Some(icon.into());\n        self\n    }\n\n    /// Set the tooltip of the button.\n    pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {\n        self.tooltip = Some((tooltip.into(), None));\n        self\n    }\n\n    /// Set the tooltip of the button with action to show keybinding.\n    pub fn tooltip_with_action(\n        mut self,\n        tooltip: impl Into<SharedString>,\n        action: &dyn Action,\n        context: Option<&str>,\n    ) -> Self {\n        self.tooltip = Some((\n            tooltip.into(),\n            Some((\n                Rc::new(action.boxed_clone()),\n                context.map(|c| c.to_string().into()),\n            )),\n        ));\n        self\n    }\n\n    /// Set true to show the loading indicator.\n    pub fn loading(mut self, loading: bool) -> Self {\n        self.loading = loading;\n        self\n    }\n\n    /// Set the button to compact mode, then padding will be reduced.\n    pub fn compact(mut self) -> Self {\n        self.compact = true;\n        self\n    }\n\n    /// Add click handler.\n    pub fn on_click(\n        mut self,\n        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_click = Some(Rc::new(handler));\n        self\n    }\n\n    /// Add hover handler, the bool parameter indicates whether the mouse is hovering.\n    pub fn on_hover(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {\n        self.on_hover = Some(Rc::new(handler));\n        self\n    }\n\n    /// Set the loading icon of the button, it will be used when loading is true.\n    ///\n    /// Default is a spinner icon.\n    pub fn loading_icon(mut self, icon: impl Into<Icon>) -> Self {\n        self.loading_icon = Some(icon.into());\n        self\n    }\n\n    /// Set the tab index of the button, it will be used to focus the button by tab key.\n    ///\n    /// Default is 0.\n    pub fn tab_index(mut self, tab_index: isize) -> Self {\n        self.tab_index = tab_index;\n        self\n    }\n\n    /// Set the tab stop of the button, if true, the button will be focusable by tab key.\n    ///\n    /// Default is true.\n    pub fn tab_stop(mut self, tab_stop: bool) -> Self {\n        self.tab_stop = tab_stop;\n        self\n    }\n\n    /// Set to show a dropdown caret icon at the end of the button.\n    pub fn dropdown_caret(mut self, dropdown_caret: bool) -> Self {\n        self.dropdown_caret = dropdown_caret;\n        self\n    }\n\n    #[inline]\n    fn clickable(&self) -> bool {\n        !(self.disabled || self.loading) && self.on_click.is_some()\n    }\n\n    #[inline]\n    fn hoverable(&self) -> bool {\n        !(self.disabled || self.loading) && self.on_hover.is_some()\n    }\n}\n\nimpl Disableable for Button {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl Selectable for Button {\n    fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        self.selected\n    }\n}\n\nimpl Sizable for Button {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl ButtonVariants for Button {\n    fn with_variant(mut self, variant: ButtonVariant) -> Self {\n        self.variant = variant;\n        self\n    }\n}\n\nimpl Styled for Button {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl ParentElement for Button {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements)\n    }\n}\n\nimpl InteractiveElement for Button {\n    fn interactivity(&mut self) -> &mut Interactivity {\n        self.base.interactivity()\n    }\n}\n\nimpl RenderOnce for Button {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let style: ButtonVariant = self.variant;\n        let clickable = self.clickable();\n        let is_disabled = self.disabled;\n        let hoverable = self.hoverable();\n        let normal_style = style.normal(self.outline, cx);\n        let icon_size = match self.size {\n            Size::Size(v) => Size::Size(v * 0.75),\n            _ => self.size,\n        };\n\n        let focus_handle = window\n            .use_keyed_state(self.id.clone(), cx, |_, cx| cx.focus_handle())\n            .read(cx)\n            .clone();\n        let is_focused = focus_handle.is_focused(window);\n\n        let rounding = match self.rounded {\n            ButtonRounded::Small => cx.theme().radius * 0.5,\n            ButtonRounded::Medium => cx.theme().radius,\n            ButtonRounded::Large => cx.theme().radius * 2.0,\n            ButtonRounded::Size(px) => px,\n            ButtonRounded::None => Pixels::ZERO,\n        };\n\n        self.base\n            .when(!self.disabled, |this| {\n                this.track_focus(\n                    &focus_handle\n                        .tab_index(self.tab_index)\n                        .tab_stop(self.tab_stop),\n                )\n            })\n            .cursor_default()\n            .flex()\n            .flex_shrink_0()\n            .items_center()\n            .justify_center()\n            .cursor_default()\n            .when(self.variant.is_link(), |this| this.cursor_pointer())\n            .when(cx.theme().shadow && normal_style.shadow, |this| {\n                this.shadow_xs()\n            })\n            .when(!style.no_padding(), |this| {\n                if self.label.is_none() && self.children.is_empty() {\n                    // Icon Button\n                    match self.size {\n                        Size::Size(px) => this.size(px),\n                        Size::XSmall => this.size_5(),\n                        Size::Small => this.size_6(),\n                        Size::Large | Size::Medium => this.size_8(),\n                    }\n                } else {\n                    // Normal Button\n                    match self.size {\n                        Size::Size(size) => this.px(size * 0.2),\n                        Size::XSmall => this.h_5().px_1().when(self.compact, |this| this.min_w_5()),\n                        Size::Small => this\n                            .h_6()\n                            .px_3()\n                            .when(self.compact, |this| this.min_w_6().px_1p5()),\n                        _ => this\n                            .h_8()\n                            .px_4()\n                            .when(self.compact, |this| this.min_w_8().px_2()),\n                    }\n                }\n            })\n            .when(self.border_corners.top_left, |this| {\n                this.rounded_tl(rounding)\n            })\n            .when(self.border_corners.top_right, |this| {\n                this.rounded_tr(rounding)\n            })\n            .when(self.border_corners.bottom_left, |this| {\n                this.rounded_bl(rounding)\n            })\n            .when(self.border_corners.bottom_right, |this| {\n                this.rounded_br(rounding)\n            })\n            .when(self.variant.is_default() || self.outline, |this| {\n                this.when(self.border_edges.left, |this| this.border_l_1())\n                    .when(self.border_edges.right, |this| this.border_r_1())\n                    .when(self.border_edges.top, |this| this.border_t_1())\n                    .when(self.border_edges.bottom, |this| this.border_b_1())\n            })\n            .text_color(normal_style.fg)\n            .when(self.selected, |this| {\n                let selected_style = style.selected(self.outline, cx);\n                this.bg(selected_style.bg)\n                    .border_color(selected_style.border)\n                    .text_color(selected_style.fg)\n            })\n            .when(!self.disabled && !self.selected, |this| {\n                this.border_color(normal_style.border)\n                    .bg(normal_style.bg)\n                    .when(normal_style.underline, |this| this.text_decoration_1())\n                    .hover(|this| {\n                        let hover_style = style.hovered(self.outline, cx);\n                        this.bg(hover_style.bg)\n                            .border_color(hover_style.border)\n                            .text_color(hover_style.fg)\n                    })\n                    .active(|this| {\n                        let active_style = style.active(self.outline, cx);\n                        this.bg(active_style.bg)\n                            .border_color(active_style.border)\n                            .text_color(active_style.fg)\n                    })\n            })\n            .when(self.disabled, |this| {\n                let disabled_style = style.disabled(self.outline, cx);\n                this.bg(disabled_style.bg)\n                    .text_color(disabled_style.fg)\n                    .border_color(disabled_style.border)\n                    .shadow_none()\n            })\n            .refine_style(&self.style)\n            .on_mouse_down(MouseButton::Left, move |_, window, cx| {\n                // Stop handle any click event when disabled.\n                // To avoid handle dropdown menu open when button is disabled.\n                if is_disabled {\n                    cx.stop_propagation();\n                    return;\n                }\n\n                // Avoid focus on mouse down.\n                window.prevent_default();\n            })\n            .when_some(self.on_click, |this, on_click| {\n                this.on_click(move |event, window, cx| {\n                    // Stop handle any click event when disabled.\n                    // To avoid handle dropdown menu open when button is disabled.\n                    if !clickable {\n                        cx.stop_propagation();\n                        return;\n                    }\n\n                    on_click(event, window, cx);\n                })\n            })\n            .when_some(self.on_hover.filter(|_| hoverable), |this, on_hover| {\n                this.on_hover(move |hovered, window, cx| {\n                    on_hover(hovered, window, cx);\n                })\n            })\n            .child({\n                h_flex()\n                    .id(\"label\")\n                    .size_full()\n                    .items_center()\n                    .justify_center()\n                    .button_text_size(self.size)\n                    .map(|this| match self.size {\n                        Size::XSmall => this.gap_1(),\n                        Size::Small => this.gap_1(),\n                        _ => this.gap_2(),\n                    })\n                    .when_some(self.icon, |this, icon| {\n                        this.child(\n                            icon.loading_icon(self.loading_icon)\n                                .loading(self.loading)\n                                .with_size(icon_size),\n                        )\n                    })\n                    .when_some(self.label, |this, label| {\n                        this.child(div().flex_none().line_height(relative(1.)).child(label))\n                    })\n                    .children(self.children)\n                    .when(self.dropdown_caret, |this| {\n                        this.justify_between().child(\n                            Icon::new(IconName::ChevronDown).xsmall().text_color(\n                                match self.disabled {\n                                    true => normal_style.fg.opacity(0.3),\n                                    false => normal_style.fg.opacity(0.5),\n                                },\n                            ),\n                        )\n                    })\n            })\n            .when(self.loading && !self.disabled, |this| {\n                this.bg(normal_style.bg.opacity(0.8))\n                    .border_color(normal_style.border.opacity(0.8))\n                    .text_color(normal_style.fg.opacity(0.8))\n            })\n            .when_some(self.tooltip, |this, (tooltip, action)| {\n                this.tooltip(move |window, cx| {\n                    Tooltip::new(tooltip.clone())\n                        .when_some(action.clone(), |this, (action, context)| {\n                            this.action(\n                                action.boxed_clone().as_ref(),\n                                context.as_ref().map(|c| c.as_ref()),\n                            )\n                        })\n                        .build(window, cx)\n                })\n            })\n            .focus_ring(is_focused, px(0.), window, cx)\n    }\n}\n\nstruct ButtonVariantStyle {\n    bg: Hsla,\n    border: Hsla,\n    fg: Hsla,\n    underline: bool,\n    shadow: bool,\n}\n\nimpl ButtonVariant {\n    fn bg_color(&self, outline: bool, cx: &mut App) -> Hsla {\n        if outline {\n            return cx.theme().input_background();\n        }\n\n        match self {\n            Self::Default => cx.theme().input_background(),\n            Self::Primary => cx.theme().button_primary,\n            Self::Secondary => cx.theme().secondary,\n            Self::Danger => cx.theme().danger.mix_oklab(cx.theme().transparent, 0.2),\n            Self::Warning => cx.theme().warning.mix_oklab(cx.theme().transparent, 0.2),\n            Self::Success => cx.theme().success.mix_oklab(cx.theme().transparent, 0.2),\n            Self::Info => cx.theme().info.mix_oklab(cx.theme().transparent, 0.2),\n            Self::Ghost | Self::Link | Self::Text => cx.theme().transparent,\n            Self::Custom(colors) => colors.color.mix_oklab(cx.theme().transparent, 0.2),\n        }\n    }\n\n    fn text_color(&self, outline: bool, cx: &mut App) -> Hsla {\n        match self {\n            Self::Default => cx.theme().foreground,\n            Self::Primary => {\n                if outline {\n                    cx.theme().button_primary\n                } else {\n                    cx.theme().button_primary_foreground\n                }\n            }\n            Self::Secondary | Self::Ghost => cx.theme().secondary_foreground,\n            Self::Danger => cx.theme().danger,\n            Self::Warning => cx.theme().warning,\n            Self::Success => cx.theme().success,\n            Self::Info => cx.theme().info,\n            Self::Link => cx.theme().link,\n            Self::Text => cx.theme().foreground,\n            Self::Custom(colors) => colors.color,\n        }\n    }\n\n    fn border_color(&self, _bg: Hsla, outline: bool, cx: &mut App) -> Hsla {\n        match self {\n            Self::Default => cx.theme().input,\n            Self::Secondary => cx.theme().border,\n            Self::Primary => cx.theme().button_primary,\n            Self::Danger => {\n                if outline {\n                    cx.theme().danger.mix_oklab(transparent_white(), 0.4)\n                } else {\n                    cx.theme().danger\n                }\n            }\n            Self::Info => {\n                if outline {\n                    cx.theme().info.mix_oklab(transparent_white(), 0.4)\n                } else {\n                    cx.theme().info\n                }\n            }\n            Self::Warning => {\n                if outline {\n                    cx.theme().warning.mix_oklab(transparent_white(), 0.4)\n                } else {\n                    cx.theme().warning\n                }\n            }\n            Self::Success => {\n                if outline {\n                    cx.theme().success.mix_oklab(transparent_white(), 0.4)\n                } else {\n                    cx.theme().success\n                }\n            }\n            Self::Ghost | Self::Link | Self::Text => cx.theme().transparent,\n            Self::Custom(colors) => {\n                if outline {\n                    colors.color.mix_oklab(transparent_white(), 0.4)\n                } else {\n                    colors.color\n                }\n            }\n        }\n    }\n\n    fn underline(&self, _: &App) -> bool {\n        match self {\n            Self::Link => true,\n            _ => false,\n        }\n    }\n\n    fn shadow(&self, outline: bool, _: &App) -> bool {\n        match self {\n            Self::Default => true,\n            Self::Primary | Self::Secondary | Self::Danger => outline,\n            Self::Custom(c) => c.shadow,\n            _ => false,\n        }\n    }\n\n    fn normal(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {\n        let bg = self.bg_color(outline, cx);\n        let border = self.border_color(bg, outline, cx);\n        let fg = self.text_color(outline, cx);\n        let underline = self.underline(cx);\n        let shadow = self.shadow(outline, cx);\n\n        ButtonVariantStyle {\n            bg,\n            border,\n            fg,\n            underline,\n            shadow,\n        }\n    }\n\n    fn hovered(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {\n        let bg = match self {\n            Self::Default => cx.theme().input.mix_oklab(cx.theme().transparent, 0.5),\n            Self::Primary => {\n                if outline {\n                    cx.theme()\n                        .button_primary\n                        .mix_oklab(cx.theme().transparent, 0.2)\n                } else {\n                    cx.theme().button_primary_hover\n                }\n            }\n            Self::Secondary => cx.theme().secondary_hover,\n            Self::Danger => {\n                if outline {\n                    cx.theme().danger.mix_oklab(cx.theme().transparent, 0.2)\n                } else {\n                    cx.theme().danger.mix_oklab(cx.theme().transparent, 0.3)\n                }\n            }\n            Self::Warning => {\n                if outline {\n                    cx.theme().warning.mix_oklab(cx.theme().transparent, 0.2)\n                } else {\n                    cx.theme().warning.mix_oklab(cx.theme().transparent, 0.3)\n                }\n            }\n            Self::Success => {\n                if outline {\n                    cx.theme().success.mix_oklab(cx.theme().transparent, 0.2)\n                } else {\n                    cx.theme().success.mix_oklab(cx.theme().transparent, 0.3)\n                }\n            }\n            Self::Info => {\n                if outline {\n                    cx.theme().info.mix_oklab(cx.theme().transparent, 0.2)\n                } else {\n                    cx.theme().info.mix_oklab(cx.theme().transparent, 0.3)\n                }\n            }\n            Self::Custom(colors) => {\n                if outline {\n                    colors.color.mix_oklab(cx.theme().transparent, 0.2)\n                } else {\n                    colors.color.mix_oklab(cx.theme().transparent, 0.3)\n                }\n            }\n            Self::Ghost => {\n                if cx.theme().mode.is_dark() {\n                    cx.theme().secondary.lighten(0.1).opacity(0.8)\n                } else {\n                    cx.theme().secondary.darken(0.1).opacity(0.8)\n                }\n            }\n            Self::Link => cx.theme().transparent,\n            Self::Text => cx.theme().transparent,\n        };\n\n        let border = self.border_color(bg, outline, cx);\n        let fg = match self {\n            Self::Link => cx.theme().link_hover,\n            _ => self.text_color(outline, cx),\n        };\n\n        let underline = self.underline(cx);\n        let shadow = self.shadow(outline, cx);\n\n        ButtonVariantStyle {\n            bg,\n            border,\n            fg,\n            underline,\n            shadow,\n        }\n    }\n\n    fn active(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {\n        let bg = match self {\n            Self::Default => cx.theme().input.mix_oklab(cx.theme().transparent, 0.7),\n            Self::Primary => {\n                if outline {\n                    cx.theme()\n                        .button_primary\n                        .mix_oklab(cx.theme().transparent, 0.4)\n                } else {\n                    cx.theme().button_primary_active\n                }\n            }\n            Self::Secondary => cx.theme().secondary_active,\n            Self::Ghost => {\n                if cx.theme().mode.is_dark() {\n                    cx.theme().secondary.lighten(0.2).opacity(0.8)\n                } else {\n                    cx.theme().secondary.darken(0.2).opacity(0.8)\n                }\n            }\n            Self::Danger => cx.theme().danger.mix_oklab(cx.theme().transparent, 0.4),\n            Self::Warning => cx.theme().warning.mix_oklab(cx.theme().transparent, 0.4),\n            Self::Success => cx.theme().success.mix_oklab(cx.theme().transparent, 0.4),\n            Self::Info => cx.theme().info.mix_oklab(cx.theme().transparent, 0.4),\n            Self::Custom(colors) => colors.color.mix_oklab(cx.theme().transparent, 0.4),\n            Self::Link => cx.theme().transparent,\n            Self::Text => cx.theme().transparent,\n        };\n        let border = self.border_color(bg, outline, cx);\n        let fg = match self {\n            Self::Link => cx.theme().link_active,\n            Self::Text => cx.theme().foreground.opacity(0.7),\n            _ => self.text_color(outline, cx),\n        };\n        let underline = self.underline(cx);\n        let shadow = self.shadow(outline, cx);\n\n        ButtonVariantStyle {\n            bg,\n            border,\n            fg,\n            underline,\n            shadow,\n        }\n    }\n\n    fn selected(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {\n        let bg = match self {\n            Self::Default => cx.theme().input.mix_oklab(cx.theme().transparent, 0.7),\n            Self::Primary => cx.theme().button_primary_active,\n            Self::Secondary | Self::Ghost => cx.theme().secondary_active,\n            Self::Danger => cx.theme().danger_active,\n            Self::Warning => cx.theme().warning_active,\n            Self::Success => cx.theme().success_active,\n            Self::Info => cx.theme().info_active,\n            Self::Link => cx.theme().transparent,\n            Self::Text => cx.theme().transparent,\n            Self::Custom(colors) => colors.active,\n        };\n\n        let border = self.border_color(bg, outline, cx);\n        let fg = match self {\n            Self::Link => cx.theme().link_active,\n            Self::Text => cx.theme().foreground.opacity(0.7),\n            _ => self.text_color(false, cx),\n        };\n        let underline = self.underline(cx);\n        let shadow = self.shadow(outline, cx);\n\n        ButtonVariantStyle {\n            bg,\n            border,\n            fg,\n            underline,\n            shadow,\n        }\n    }\n\n    fn disabled(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {\n        let bg = match self {\n            Self::Default | Self::Link | Self::Ghost | Self::Text => cx.theme().transparent,\n            Self::Primary => cx.theme().button_primary.opacity(0.15),\n            Self::Danger => cx.theme().danger.opacity(0.15),\n            Self::Warning => cx.theme().warning.opacity(0.15),\n            Self::Success => cx.theme().success.opacity(0.15),\n            Self::Info => cx.theme().info.opacity(0.15),\n            Self::Secondary => cx.theme().secondary.opacity(1.5),\n            Self::Custom(style) => style.color.opacity(0.15),\n        };\n        let fg = cx.theme().muted_foreground.opacity(0.5);\n        let (bg, border) = if outline {\n            (\n                cx.theme().input_background().opacity(0.5),\n                cx.theme().border.opacity(0.5),\n            )\n        } else if let Self::Default = self {\n            (\n                cx.theme().input_background().opacity(0.5),\n                cx.theme().input.opacity(0.5),\n            )\n        } else {\n            (bg, bg)\n        };\n\n        let underline = self.underline(cx);\n        let shadow = false;\n\n        ButtonVariantStyle {\n            bg,\n            border,\n            fg,\n            underline,\n            shadow,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[gpui::test]\n    fn test_button_builder(_cx: &mut gpui::TestAppContext) {\n        let button = Button::new(\"complex-button\")\n            .label(\"Save Changes\")\n            .primary()\n            .outline()\n            .large()\n            .tooltip(\"Click to save\")\n            .compact()\n            .loading(false)\n            .disabled(false)\n            .selected(false)\n            .tab_index(1)\n            .tab_stop(true)\n            .dropdown_caret(false)\n            .rounded(ButtonRounded::Medium)\n            .on_click(|_, _, _| {});\n\n        assert_eq!(button.label, Some(\"Save Changes\".into()));\n        assert_eq!(button.variant, ButtonVariant::Primary);\n        assert!(button.outline);\n        assert_eq!(button.size, Size::Large);\n        assert!(button.tooltip.is_some());\n        assert!(button.compact);\n        assert!(!button.loading);\n        assert!(!button.disabled);\n        assert!(!button.selected);\n        assert_eq!(button.tab_index, 1);\n        assert!(button.tab_stop);\n        assert!(!button.dropdown_caret);\n        assert!(matches!(button.rounded, ButtonRounded::Medium));\n    }\n\n    #[gpui::test]\n    fn test_button_clickable_logic(_cx: &mut gpui::TestAppContext) {\n        // Button with click handler should be clickable\n        let clickable = Button::new(\"test\").on_click(|_, _, _| {});\n        assert!(clickable.clickable());\n\n        // Disabled button should not be clickable\n        let disabled = Button::new(\"test\").disabled(true).on_click(|_, _, _| {});\n        assert!(!disabled.clickable());\n\n        // Loading button should not be clickable\n        let loading = Button::new(\"test\").loading(true).on_click(|_, _, _| {});\n        assert!(!loading.clickable());\n    }\n\n    #[gpui::test]\n    fn test_button_variant_methods(_cx: &mut gpui::TestAppContext) {\n        // Test variant check methods\n        assert!(ButtonVariant::Link.is_link());\n        assert!(ButtonVariant::Text.is_text());\n        assert!(ButtonVariant::Ghost.is_ghost());\n\n        // Test no_padding logic\n        assert!(ButtonVariant::Link.no_padding());\n        assert!(ButtonVariant::Text.no_padding());\n        assert!(!ButtonVariant::Ghost.no_padding());\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/button/button_group.rs",
    "content": "use gpui::{\n    div, prelude::FluentBuilder as _, App, Axis, Corners, Edges, ElementId, InteractiveElement,\n    IntoElement, ParentElement, RenderOnce, StatefulInteractiveElement as _, StyleRefinement,\n    Styled, Window,\n};\nuse std::{cell::Cell, rc::Rc};\n\nuse crate::{\n    button::{Button, ButtonVariant, ButtonVariants},\n    Disableable, Sizable, Size, StyledExt,\n};\n\n/// A ButtonGroup element, to wrap multiple buttons in a group.\n#[derive(IntoElement)]\npub struct ButtonGroup {\n    id: ElementId,\n    style: StyleRefinement,\n    children: Vec<Button>,\n    pub(super) multiple: bool,\n    pub(super) disabled: bool,\n    pub(super) layout: Axis,\n\n    // The button props\n    pub(super) compact: bool,\n    pub(super) outline: bool,\n    pub(super) variant: Option<ButtonVariant>,\n    pub(super) size: Option<Size>,\n\n    on_click: Option<Box<dyn Fn(&Vec<usize>, &mut Window, &mut App) + 'static>>,\n}\n\nimpl Disableable for ButtonGroup {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl ButtonGroup {\n    /// Creates a new ButtonGroup.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default(),\n            children: Vec::new(),\n            variant: None,\n            size: None,\n            compact: false,\n            outline: false,\n            multiple: false,\n            disabled: false,\n            layout: Axis::Horizontal,\n            on_click: None,\n        }\n    }\n\n    /// Adds a button as a child to the ButtonGroup.\n    pub fn child(mut self, child: Button) -> Self {\n        self.children.push(child.disabled(self.disabled));\n        self\n    }\n\n    /// Adds multiple buttons as children to the ButtonGroup.\n    pub fn children(mut self, children: impl IntoIterator<Item = Button>) -> Self {\n        self.children.extend(children);\n        self\n    }\n\n    /// With the multiple selection mode, default is false (single selection).\n    pub fn multiple(mut self, multiple: bool) -> Self {\n        self.multiple = multiple;\n        self\n    }\n\n    /// Set the layout of the button group. Default is `Axis::Horizontal`.\n    pub fn layout(mut self, layout: Axis) -> Self {\n        self.layout = layout;\n        self\n    }\n\n    /// With the compact mode for the ButtonGroup.\n    ///\n    /// See also: [`Button::compact()`]\n    pub fn compact(mut self) -> Self {\n        self.compact = true;\n        self\n    }\n\n    /// With the outline mode for the ButtonGroup.\n    ///\n    /// See also: [`Button::outline()`]\n    pub fn outline(mut self) -> Self {\n        self.outline = true;\n        self\n    }\n\n    /// Sets the on_click handler for the ButtonGroup.\n    ///\n    /// The handler first argument is a vector of the selected button indices.\n    ///\n    /// The `&Vec<usize>` is the indices of the clicked (selected in `multiple` mode) buttons.\n    /// For example: `[0, 2, 3]` is means the first, third and fourth buttons are clicked.\n    ///\n    /// ```ignore\n    /// ButtonGroup::new(\"size-button\")\n    ///    .child(Button::new(\"large\").label(\"Large\").selected(self.size == Size::Large))\n    ///    .child(Button::new(\"medium\").label(\"Medium\").selected(self.size == Size::Medium))\n    ///    .child(Button::new(\"small\").label(\"Small\").selected(self.size == Size::Small))\n    ///    .on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {\n    ///        if clicks.contains(&0) {\n    ///            view.size = Size::Large;\n    ///        } else if clicks.contains(&1) {\n    ///            view.size = Size::Medium;\n    ///        } else if clicks.contains(&2) {\n    ///            view.size = Size::Small;\n    ///        }\n    ///        cx.notify();\n    ///    }))\n    /// ```\n    pub fn on_click(\n        mut self,\n        handler: impl Fn(&Vec<usize>, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_click = Some(Box::new(handler));\n        self\n    }\n}\n\nimpl Sizable for ButtonGroup {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = Some(size.into());\n        self\n    }\n}\n\nimpl Styled for ButtonGroup {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl ButtonVariants for ButtonGroup {\n    fn with_variant(mut self, variant: ButtonVariant) -> Self {\n        self.variant = Some(variant);\n        self\n    }\n}\n\nimpl RenderOnce for ButtonGroup {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        let children_len = self.children.len();\n        let mut selected_ixs: Vec<usize> = Vec::new();\n        let state = Rc::new(Cell::new(None));\n\n        for (ix, child) in self.children.iter().enumerate() {\n            if child.selected {\n                selected_ixs.push(ix);\n            }\n        }\n\n        let vertical = self.layout == Axis::Vertical;\n\n        div()\n            .id(self.id)\n            .flex()\n            .when(vertical, |this| this.flex_col().justify_center())\n            .when(!vertical, |this| this.items_center())\n            .refine_style(&self.style)\n            .children(\n                self.children\n                    .into_iter()\n                    .enumerate()\n                    .map(|(child_index, child)| {\n                        let state = Rc::clone(&state);\n                        let child = if children_len == 1 {\n                            child\n                        } else if child_index == 0 {\n                            // First\n                            child\n                                .border_corners(Corners {\n                                    top_left: true,\n                                    top_right: vertical,\n                                    bottom_left: !vertical,\n                                    bottom_right: false,\n                                })\n                                .border_edges(Edges {\n                                    left: true,\n                                    top: true,\n                                    right: true,\n                                    bottom: true,\n                                })\n                        } else if child_index == children_len - 1 {\n                            // Last\n                            child\n                                .border_edges(Edges {\n                                    left: vertical,\n                                    top: !vertical,\n                                    right: true,\n                                    bottom: true,\n                                })\n                                .border_corners(Corners {\n                                    top_left: false,\n                                    top_right: !vertical,\n                                    bottom_left: vertical,\n                                    bottom_right: true,\n                                })\n                        } else {\n                            // Middle\n                            child\n                                .border_corners(Corners::all(false))\n                                .border_edges(Edges {\n                                    left: vertical,\n                                    top: !vertical,\n                                    right: true,\n                                    bottom: true,\n                                })\n                        }\n                        .when_some(self.size, |this, size| this.with_size(size))\n                        .when_some(self.variant, |this, variant| this.with_variant(variant))\n                        .when(self.compact, |this| this.compact())\n                        .when(self.outline, |this| this.outline())\n                        .when(self.on_click.is_some(), |this| {\n                            this.on_click(move |_, _, _| {\n                                state.set(Some(child_index));\n                            })\n                        });\n\n                        child\n                    }),\n            )\n            .when_some(\n                self.on_click.filter(|_| !self.disabled),\n                move |this, on_click| {\n                    this.on_click(move |_, window, cx| {\n                        let mut selected_ixs = selected_ixs.clone();\n                        if let Some(ix) = state.get() {\n                            if self.multiple {\n                                if let Some(pos) = selected_ixs.iter().position(|&i| i == ix) {\n                                    selected_ixs.remove(pos);\n                                } else {\n                                    selected_ixs.push(ix);\n                                }\n                            } else {\n                                selected_ixs.clear();\n                                selected_ixs.push(ix);\n                            }\n                        }\n\n                        on_click(&selected_ixs, window, cx);\n                    })\n                },\n            )\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use gpui::Axis;\n\n    #[gpui::test]\n    fn test_button_group_builder(_cx: &mut gpui::TestAppContext) {\n        let group = ButtonGroup::new(\"complex-group\")\n            .child(Button::new(\"btn1\").label(\"One\"))\n            .child(Button::new(\"btn2\").label(\"Two\"))\n            .child(Button::new(\"btn3\").label(\"Three\"))\n            .primary()\n            .large()\n            .outline()\n            .compact()\n            .multiple(true)\n            .layout(Axis::Vertical)\n            .disabled(false)\n            .on_click(|_, _, _| {});\n\n        assert_eq!(group.children.len(), 3);\n        assert_eq!(group.variant, Some(ButtonVariant::Primary));\n        assert_eq!(group.size, Some(Size::Large));\n        assert!(group.outline);\n        assert!(group.compact);\n        assert!(group.multiple);\n        assert_eq!(group.layout, Axis::Vertical);\n        assert!(!group.disabled);\n        assert!(group.on_click.is_some());\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/button/button_icon.rs",
    "content": "use crate::{Icon, Sizable, Size, progress::ProgressCircle, spinner::Spinner};\nuse gpui::{App, IntoElement, RenderOnce, Window, prelude::FluentBuilder};\n\n/// Button icon which can be an Icon, Spinner, or Progress use for `icon` method of Button.\n#[doc(hidden)]\n#[derive(IntoElement)]\npub struct ButtonIcon {\n    icon: ButtonIconVariant,\n    loading_icon: Option<Icon>,\n    loading: bool,\n    size: Size,\n}\n\nimpl<T> From<T> for ButtonIcon\nwhere\n    T: Into<ButtonIconVariant>,\n{\n    fn from(icon: T) -> Self {\n        ButtonIcon::new(icon)\n    }\n}\n\nimpl ButtonIcon {\n    /// Creates a new ButtonIcon with the given icon.\n    pub fn new(icon: impl Into<ButtonIconVariant>) -> Self {\n        Self {\n            icon: icon.into(),\n            loading_icon: None,\n            loading: false,\n            size: Size::Medium,\n        }\n    }\n\n    pub(crate) fn loading_icon(mut self, icon: Option<Icon>) -> Self {\n        self.loading_icon = icon;\n        self\n    }\n\n    pub(crate) fn loading(mut self, loading: bool) -> Self {\n        self.loading = loading;\n        self\n    }\n}\n\nimpl Sizable for ButtonIcon {\n    fn with_size(mut self, size: impl Into<crate::Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\n/// Button icon which can be an Icon, Spinner, Progress, or ProgressCircle use for `icon` method of Button.\n#[doc(hidden)]\n#[derive(IntoElement)]\npub enum ButtonIconVariant {\n    Icon(Icon),\n    Spinner(Spinner),\n    Progress(ProgressCircle),\n}\n\nimpl<T> From<T> for ButtonIconVariant\nwhere\n    T: Into<Icon>,\n{\n    fn from(icon: T) -> Self {\n        Self::Icon(icon.into())\n    }\n}\n\nimpl From<Spinner> for ButtonIconVariant {\n    fn from(spinner: Spinner) -> Self {\n        Self::Spinner(spinner)\n    }\n}\n\nimpl From<ProgressCircle> for ButtonIconVariant {\n    fn from(progress: ProgressCircle) -> Self {\n        Self::Progress(progress)\n    }\n}\n\nimpl ButtonIconVariant {\n    /// Returns true if the ButtonIconKind is an Icon.\n    #[inline]\n    pub(crate) fn is_spinner(&self) -> bool {\n        matches!(self, Self::Spinner(_))\n    }\n\n    /// Returns true if the ButtonIconKind is a Progress or ProgressCircle.\n    #[inline]\n    pub(crate) fn is_progress(&self) -> bool {\n        matches!(self, Self::Progress(_))\n    }\n}\n\nimpl Sizable for ButtonIconVariant {\n    fn with_size(self, size: impl Into<crate::Size>) -> Self {\n        match self {\n            Self::Icon(icon) => Self::Icon(icon.with_size(size)),\n            Self::Spinner(spinner) => Self::Spinner(spinner.with_size(size)),\n            Self::Progress(progress) => Self::Progress(progress.with_size(size)),\n        }\n    }\n}\n\nimpl RenderOnce for ButtonIconVariant {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        match self {\n            Self::Icon(icon) => icon.into_any_element(),\n            Self::Spinner(spinner) => spinner.into_any_element(),\n            Self::Progress(progress) => progress.into_any_element(),\n        }\n    }\n}\n\nimpl RenderOnce for ButtonIcon {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        if self.loading {\n            if self.icon.is_spinner() || self.icon.is_progress() {\n                self.icon.with_size(self.size).into_any_element()\n            } else {\n                Spinner::new()\n                    .when_some(self.loading_icon, |this, icon| this.icon(icon))\n                    .with_size(self.size)\n                    .into_any_element()\n            }\n        } else {\n            self.icon.with_size(self.size).into_any_element()\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::IconName;\n\n    #[gpui::test]\n    fn test_button_icon_builder(_cx: &mut gpui::TestAppContext) {\n        let custom_icon = Icon::new(IconName::Loader);\n        let icon = ButtonIcon::new(IconName::Plus)\n            .loading(true)\n            .loading_icon(Some(custom_icon))\n            .large();\n\n        assert!(icon.loading);\n        assert!(icon.loading_icon.is_some());\n        assert_eq!(icon.size, Size::Large);\n    }\n\n    #[gpui::test]\n    fn test_button_icon_variant_types(_cx: &mut gpui::TestAppContext) {\n        // Test Icon variant\n        let icon_variant = ButtonIconVariant::Icon(Icon::new(IconName::Plus));\n        assert!(!icon_variant.is_spinner());\n        assert!(!icon_variant.is_progress());\n\n        // Test Spinner variant\n        let spinner_variant = ButtonIconVariant::Spinner(Spinner::new());\n        assert!(spinner_variant.is_spinner());\n        assert!(!spinner_variant.is_progress());\n\n        // Test Progress variant\n        let progress_variant = ButtonIconVariant::Progress(ProgressCircle::new(75));\n        assert!(!progress_variant.is_spinner());\n        assert!(progress_variant.is_progress());\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/button/dropdown_button.rs",
    "content": "use gpui::{\n    App, Context, Corner, Corners, Edges, ElementId, InteractiveElement as _, IntoElement,\n    ParentElement, RenderOnce, StyleRefinement, Styled, Window, div, prelude::FluentBuilder,\n};\n\nuse crate::{\n    Disableable, IconName, Selectable, Sizable, Size, StyledExt as _,\n    menu::{DropdownMenu, PopupMenu},\n};\n\nuse super::{Button, ButtonRounded, ButtonVariant, ButtonVariants};\n\n#[derive(IntoElement)]\npub struct DropdownButton {\n    id: ElementId,\n    style: StyleRefinement,\n    button: Option<Button>,\n    menu:\n        Option<Box<dyn Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static>>,\n    selected: bool,\n    disabled: bool,\n    // The button props\n    compact: bool,\n    outline: bool,\n    loading: bool,\n    variant: ButtonVariant,\n    size: Size,\n    rounded: ButtonRounded,\n    anchor: Corner,\n}\n\nimpl DropdownButton {\n    /// Create a new DropdownButton.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default(),\n            button: None,\n            menu: None,\n            selected: false,\n            disabled: false,\n            compact: false,\n            outline: false,\n            loading: false,\n            variant: ButtonVariant::default(),\n            size: Size::default(),\n            rounded: ButtonRounded::default(),\n            anchor: Corner::TopRight,\n        }\n    }\n\n    /// Set the left button of the dropdown button.\n    pub fn button(mut self, button: Button) -> Self {\n        self.button = Some(button);\n        self\n    }\n\n    /// Set the dropdown menu of the button.\n    pub fn dropdown_menu(\n        mut self,\n        menu: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,\n    ) -> Self {\n        self.menu = Some(Box::new(menu));\n        self\n    }\n\n    /// Set the dropdown menu of the button with anchor corner.\n    pub fn dropdown_menu_with_anchor(\n        mut self,\n        anchor: impl Into<Corner>,\n        menu: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,\n    ) -> Self {\n        self.menu = Some(Box::new(menu));\n        self.anchor = anchor.into();\n        self\n    }\n\n    /// Set the rounded style of the button.\n    pub fn rounded(mut self, rounded: impl Into<ButtonRounded>) -> Self {\n        self.rounded = rounded.into();\n        self\n    }\n\n    /// Set the button to compact style.\n    ///\n    /// See also: [`Button::compact`]\n    pub fn compact(mut self) -> Self {\n        self.compact = true;\n        self\n    }\n\n    /// Set the button to outline style.\n    ///\n    /// See also: [`Button::outline`]\n    pub fn outline(mut self) -> Self {\n        self.outline = true;\n        self\n    }\n\n    /// Set the button to loading state.\n    pub fn loading(mut self, loading: bool) -> Self {\n        self.loading = loading;\n        self\n    }\n}\n\nimpl Disableable for DropdownButton {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl Styled for DropdownButton {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Sizable for DropdownButton {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl ButtonVariants for DropdownButton {\n    fn with_variant(mut self, variant: ButtonVariant) -> Self {\n        self.variant = variant;\n        self\n    }\n}\n\nimpl Selectable for DropdownButton {\n    fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        self.selected\n    }\n}\n\nimpl RenderOnce for DropdownButton {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        let rounded = self.variant.is_ghost() && !self.selected;\n\n        div()\n            .id(self.id)\n            .h_flex()\n            .refine_style(&self.style)\n            .when_some(self.button, |this, button| {\n                this.child(\n                    button\n                        .rounded(self.rounded)\n                        .border_corners(Corners {\n                            top_left: true,\n                            top_right: rounded,\n                            bottom_left: true,\n                            bottom_right: rounded,\n                        })\n                        .border_edges(Edges {\n                            left: true,\n                            top: true,\n                            right: true,\n                            bottom: true,\n                        })\n                        .loading(self.loading)\n                        .selected(self.selected)\n                        .disabled(self.disabled || self.loading)\n                        .when(self.compact, |this| this.compact())\n                        .when(self.outline, |this| this.outline())\n                        .with_size(self.size)\n                        .with_variant(self.variant),\n                )\n                .when_some(self.menu, |this, menu| {\n                    this.child(\n                        Button::new(\"popup\")\n                            .icon(IconName::ChevronDown)\n                            .rounded(self.rounded)\n                            .border_edges(Edges {\n                                left: rounded,\n                                top: true,\n                                right: true,\n                                bottom: true,\n                            })\n                            .border_corners(Corners {\n                                top_left: rounded,\n                                top_right: true,\n                                bottom_left: rounded,\n                                bottom_right: true,\n                            })\n                            .selected(self.selected)\n                            .disabled(self.disabled || self.loading)\n                            .when(self.compact, |this| this.compact())\n                            .when(self.outline, |this| this.outline())\n                            .with_size(self.size)\n                            .with_variant(self.variant)\n                            .dropdown_menu_with_anchor(self.anchor, menu),\n                    )\n                })\n            })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use gpui::Corner;\n\n    #[gpui::test]\n    fn test_dropdown_button_builder(_cx: &mut gpui::TestAppContext) {\n        let button = Button::new(\"inner\").label(\"Action\");\n        let dropdown = DropdownButton::new(\"complex-dropdown\")\n            .button(button)\n            .primary()\n            .outline()\n            .large()\n            .compact()\n            .loading(false)\n            .disabled(false)\n            .selected(false)\n            .rounded(ButtonRounded::Medium)\n            .dropdown_menu_with_anchor(Corner::BottomLeft, |menu, _, _| menu);\n\n        assert!(dropdown.button.is_some());\n        assert_eq!(dropdown.variant, ButtonVariant::Primary);\n        assert!(dropdown.outline);\n        assert_eq!(dropdown.size, Size::Large);\n        assert!(dropdown.compact);\n        assert!(!dropdown.loading);\n        assert!(!dropdown.disabled);\n        assert!(!dropdown.selected);\n        assert!(matches!(dropdown.rounded, ButtonRounded::Medium));\n        assert!(dropdown.menu.is_some());\n        assert_eq!(dropdown.anchor, Corner::BottomLeft);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/button/mod.rs",
    "content": "mod button;\nmod button_group;\nmod button_icon;\nmod dropdown_button;\nmod toggle;\n\npub use button::*;\npub use button_group::*;\npub(crate) use button_icon::*;\npub use dropdown_button::*;\npub use toggle::*;\n"
  },
  {
    "path": "crates/ui/src/button/toggle.rs",
    "content": "use std::{cell::Cell, rc::Rc};\n\nuse gpui::{\n    AnyElement, App, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce,\n    SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, div,\n    prelude::FluentBuilder as _,\n};\nuse smallvec::{SmallVec, smallvec};\n\nuse crate::{ActiveTheme, Disableable, Icon, Sizable, Size, StyledExt, h_flex};\n\n#[derive(Default, Copy, Debug, Clone, PartialEq, Eq, Hash)]\npub enum ToggleVariant {\n    #[default]\n    Ghost,\n    Outline,\n}\n\npub trait ToggleVariants: Sized {\n    /// Set the variant of the toggle.\n    fn with_variant(self, variant: ToggleVariant) -> Self;\n    /// Set the variant to ghost.\n    fn ghost(self) -> Self {\n        self.with_variant(ToggleVariant::Ghost)\n    }\n    /// Set the variant to outline.\n    fn outline(self) -> Self {\n        self.with_variant(ToggleVariant::Outline)\n    }\n}\n\n#[derive(IntoElement)]\npub struct Toggle {\n    id: ElementId,\n    style: StyleRefinement,\n    checked: bool,\n    size: Size,\n    variant: ToggleVariant,\n    disabled: bool,\n    children: SmallVec<[AnyElement; 1]>,\n    on_click: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,\n}\n\nimpl Toggle {\n    /// Create a new Toggle element.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default(),\n            checked: false,\n            size: Size::default(),\n            variant: ToggleVariant::default(),\n            disabled: false,\n            children: smallvec![],\n            on_click: None,\n        }\n    }\n\n    /// Add a label to the toggle.\n    pub fn label(mut self, label: impl Into<SharedString>) -> Self {\n        let label: SharedString = label.into();\n        self.children.push(label.into_any_element());\n        self\n    }\n\n    /// Add icon to the toggle.\n    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {\n        let icon: Icon = icon.into();\n        self.children.push(icon.into());\n        self\n    }\n\n    /// Set the checked state of the toggle, default: false\n    pub fn checked(mut self, checked: bool) -> Self {\n        self.checked = checked;\n        self\n    }\n\n    /// Set the callback to be called when the toggle is clicked.\n    ///\n    /// The `&bool` parameter represents the new checked state of the toggle.\n    pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {\n        self.on_click = Some(Box::new(handler));\n        self\n    }\n}\n\nimpl ToggleVariants for Toggle {\n    fn with_variant(mut self, variant: ToggleVariant) -> Self {\n        self.variant = variant;\n        self\n    }\n}\n\nimpl ParentElement for Toggle {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Disableable for Toggle {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl Sizable for Toggle {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Styled for Toggle {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Toggle {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let checked = self.checked;\n        let disabled = self.disabled;\n        let hoverable = !disabled && !checked;\n\n        div()\n            .id(self.id)\n            .flex()\n            .flex_row()\n            .items_center()\n            .justify_center()\n            .map(|this| match self.size {\n                Size::XSmall => this.min_w_5().h_5().px_0p5().text_xs(),\n                Size::Small => this.min_w_6().h_6().px_1().text_sm(),\n                Size::Large => this.min_w_9().h_9().px_3().text_lg(),\n                _ => this.min_w_8().h_8().px_2(),\n            })\n            .rounded(cx.theme().radius)\n            .when(self.variant == ToggleVariant::Outline, |this| {\n                this.border_1()\n                    .border_color(cx.theme().border)\n                    .bg(cx.theme().background)\n                    .when(cx.theme().shadow, |this| this.shadow_xs())\n            })\n            .when(hoverable, |this| {\n                this.hover(|this| {\n                    this.bg(cx.theme().accent)\n                        .text_color(cx.theme().accent_foreground)\n                })\n            })\n            .when(checked, |this| {\n                this.bg(cx.theme().accent)\n                    .text_color(cx.theme().accent_foreground)\n            })\n            .refine_style(&self.style)\n            .children(self.children)\n            .when(!disabled, |this| {\n                this.when_some(self.on_click, |this, on_click| {\n                    this.on_click(move |_, window, cx| on_click(&!checked, window, cx))\n                })\n            })\n    }\n}\n\n/// A group of toggles.\n#[derive(IntoElement)]\npub struct ToggleGroup {\n    id: ElementId,\n    style: StyleRefinement,\n    size: Size,\n    variant: ToggleVariant,\n    disabled: bool,\n    items: Vec<Toggle>,\n    on_click: Option<Rc<dyn Fn(&Vec<bool>, &mut Window, &mut App) + 'static>>,\n}\n\nimpl ToggleGroup {\n    /// Create a new ToggleGroup element.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default(),\n            size: Size::default(),\n            variant: ToggleVariant::default(),\n            disabled: false,\n            items: Vec::new(),\n            on_click: None,\n        }\n    }\n\n    /// Add a child [`Toggle`] to the group.\n    pub fn child(mut self, toggle: impl Into<Toggle>) -> Self {\n        self.items.push(toggle.into());\n        self\n    }\n\n    /// Add multiple [`Toggle`]s to the group.\n    pub fn children(mut self, children: impl IntoIterator<Item = impl Into<Toggle>>) -> Self {\n        self.items.extend(children.into_iter().map(Into::into));\n        self\n    }\n\n    /// Set the callback to be called when the toggle group changes.\n    ///\n    /// The `&Vec<bool>` parameter represents the new check state of each [`Toggle`] in the group.\n    pub fn on_click(\n        mut self,\n        on_click: impl Fn(&Vec<bool>, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_click = Some(Rc::new(on_click));\n        self\n    }\n}\n\nimpl Sizable for ToggleGroup {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl ToggleVariants for ToggleGroup {\n    fn with_variant(mut self, variant: ToggleVariant) -> Self {\n        self.variant = variant;\n        self\n    }\n}\n\nimpl Disableable for ToggleGroup {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl Styled for ToggleGroup {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for ToggleGroup {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        let disabled = self.disabled;\n        let checks = self\n            .items\n            .iter()\n            .map(|item| item.checked)\n            .collect::<Vec<bool>>();\n        let state = Rc::new(Cell::new(None));\n\n        h_flex()\n            .id(self.id)\n            .gap_1()\n            .refine_style(&self.style)\n            .children(self.items.into_iter().enumerate().map({\n                |(ix, item)| {\n                    let state = state.clone();\n                    item.disabled(disabled)\n                        .with_size(self.size)\n                        .with_variant(self.variant)\n                        .on_click(move |_, _, _| {\n                            state.set(Some(ix));\n                        })\n                }\n            }))\n            .when(!disabled, |this| {\n                this.when_some(self.on_click, |this, on_click| {\n                    this.on_click(move |_, window, cx| {\n                        if let Some(ix) = state.get() {\n                            let mut checks = checks.clone();\n                            checks[ix] = !checks[ix];\n                            on_click(&checks, window, cx);\n                        }\n                    })\n                })\n            })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::IconName;\n\n    #[gpui::test]\n    fn test_toggle_builder(_cx: &mut gpui::TestAppContext) {\n        let toggle = Toggle::new(\"complex-toggle\")\n            .label(\"Enable Feature\")\n            .icon(IconName::Check)\n            .checked(true)\n            .outline()\n            .large()\n            .disabled(false)\n            .on_click(|_, _, _| {});\n\n        assert_eq!(toggle.children.len(), 2); // label + icon\n        assert!(toggle.checked);\n        assert_eq!(toggle.variant, ToggleVariant::Outline);\n        assert_eq!(toggle.size, Size::Large);\n        assert!(!toggle.disabled);\n        assert!(toggle.on_click.is_some());\n    }\n\n    #[gpui::test]\n    fn test_toggle_group_builder(_cx: &mut gpui::TestAppContext) {\n        let group = ToggleGroup::new(\"complex-group\")\n            .child(Toggle::new(\"toggle1\").label(\"Option 1\"))\n            .child(Toggle::new(\"toggle2\").label(\"Option 2\").checked(true))\n            .child(Toggle::new(\"toggle3\").label(\"Option 3\"))\n            .outline()\n            .large()\n            .disabled(false)\n            .on_click(|_, _, _| {});\n\n        assert_eq!(group.items.len(), 3);\n        assert_eq!(group.variant, ToggleVariant::Outline);\n        assert_eq!(group.size, Size::Large);\n        assert!(!group.disabled);\n        assert!(group.on_click.is_some());\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/chart/area_chart.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{App, Background, Bounds, Hsla, Pixels, SharedString, Window, px};\nuse gpui_component_macros::IntoPlot;\nuse num_traits::{Num, ToPrimitive};\n\nuse crate::{\n    ActiveTheme,\n    plot::{\n        AXIS_GAP, Grid, Plot, PlotAxis, StrokeStyle,\n        scale::{Scale, ScaleLinear, ScalePoint, Sealed},\n        shape::Area,\n    },\n};\n\nuse super::build_point_x_labels;\n\n#[derive(IntoPlot)]\npub struct AreaChart<T, X, Y>\nwhere\n    T: 'static,\n    X: Clone + PartialEq + Into<SharedString> + 'static,\n    Y: Clone + Copy + PartialOrd + Num + ToPrimitive + Sealed + 'static,\n{\n    data: Vec<T>,\n    x: Option<Rc<dyn Fn(&T) -> X>>,\n    y: Vec<Rc<dyn Fn(&T) -> Y>>,\n    strokes: Vec<Hsla>,\n    stroke_styles: Vec<StrokeStyle>,\n    fills: Vec<Background>,\n    tick_margin: usize,\n    x_axis: bool,\n    grid: bool,\n}\n\nimpl<T, X, Y> AreaChart<T, X, Y>\nwhere\n    X: Clone + PartialEq + Into<SharedString> + 'static,\n    Y: Clone + Copy + PartialOrd + Num + ToPrimitive + Sealed + 'static,\n{\n    pub fn new<I>(data: I) -> Self\n    where\n        I: IntoIterator<Item = T>,\n    {\n        Self {\n            data: data.into_iter().collect(),\n            stroke_styles: vec![],\n            strokes: vec![],\n            fills: vec![],\n            tick_margin: 1,\n            x: None,\n            y: vec![],\n            x_axis: true,\n            grid: true,\n        }\n    }\n\n    pub fn x(mut self, x: impl Fn(&T) -> X + 'static) -> Self {\n        self.x = Some(Rc::new(x));\n        self\n    }\n\n    pub fn y(mut self, y: impl Fn(&T) -> Y + 'static) -> Self {\n        self.y.push(Rc::new(y));\n        self\n    }\n\n    pub fn stroke(mut self, stroke: impl Into<Hsla>) -> Self {\n        self.strokes.push(stroke.into());\n        self\n    }\n\n    pub fn fill(mut self, fill: impl Into<Background>) -> Self {\n        self.fills.push(fill.into());\n        self\n    }\n\n    pub fn natural(mut self) -> Self {\n        self.stroke_styles.push(StrokeStyle::Natural);\n        self\n    }\n\n    pub fn linear(mut self) -> Self {\n        self.stroke_styles.push(StrokeStyle::Linear);\n        self\n    }\n\n    pub fn step_after(mut self) -> Self {\n        self.stroke_styles.push(StrokeStyle::StepAfter);\n        self\n    }\n\n    pub fn tick_margin(mut self, tick_margin: usize) -> Self {\n        self.tick_margin = tick_margin;\n        self\n    }\n\n    /// Show or hide the x-axis line and labels.\n    ///\n    /// Default is true.\n    pub fn x_axis(mut self, x_axis: bool) -> Self {\n        self.x_axis = x_axis;\n        self\n    }\n\n    pub fn grid(mut self, grid: bool) -> Self {\n        self.grid = grid;\n        self\n    }\n}\n\nimpl<T, X, Y> Plot for AreaChart<T, X, Y>\nwhere\n    X: Clone + PartialEq + Into<SharedString> + 'static,\n    Y: Clone + Copy + PartialOrd + Num + ToPrimitive + Sealed + 'static,\n{\n    fn paint(&mut self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App) {\n        let Some(x_fn) = self.x.as_ref() else {\n            return;\n        };\n\n        if self.y.len() == 0 {\n            return;\n        }\n\n        let width = bounds.size.width.as_f32();\n        let axis_gap = if self.x_axis { AXIS_GAP } else { 0. };\n        let height = bounds.size.height.as_f32() - axis_gap;\n\n        // X scale\n        let x = ScalePoint::new(self.data.iter().map(|v| x_fn(v)).collect(), vec![0., width]);\n\n        // Y scale\n        let domain = self\n            .data\n            .iter()\n            .flat_map(|v| self.y.iter().map(|y_fn| y_fn(v)))\n            .chain(Some(Y::zero()))\n            .collect::<Vec<_>>();\n        let y = ScaleLinear::new(domain, vec![height, 10.]);\n\n        // Draw X axis\n        let mut axis = PlotAxis::new().stroke(cx.theme().border);\n        if self.x_axis {\n            let labels = build_point_x_labels(\n                &self.data,\n                x_fn.as_ref(),\n                &x,\n                self.tick_margin,\n                cx.theme().muted_foreground,\n            );\n            axis = axis.x(height).x_label(labels);\n        }\n        axis.paint(&bounds, window, cx);\n\n        // Draw grid\n        if self.grid {\n            Grid::new()\n                .y((0..=3).map(|i| height * i as f32 / 4.0).collect())\n                .stroke(cx.theme().border)\n                .dash_array(&[px(4.), px(2.)])\n                .paint(&bounds, window);\n        }\n\n        // Draw area\n        for (i, y_fn) in self.y.iter().enumerate() {\n            let x = x.clone();\n            let y = y.clone();\n            let x_fn = x_fn.clone();\n            let y_fn = y_fn.clone();\n\n            let fill = *self\n                .fills\n                .get(i)\n                .unwrap_or(&cx.theme().chart_2.opacity(0.4).into());\n\n            let stroke = *self.strokes.get(i).unwrap_or(&cx.theme().chart_2);\n\n            let stroke_style = *self\n                .stroke_styles\n                .get(i)\n                .unwrap_or(self.stroke_styles.first().unwrap_or(&Default::default()));\n\n            Area::new()\n                .data(&self.data)\n                .x(move |d| x.tick(&x_fn(d)))\n                .y0(height)\n                .y1(move |d| y.tick(&y_fn(d)))\n                .stroke(stroke)\n                .stroke_style(stroke_style)\n                .fill(fill)\n                .paint(&bounds, window);\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/chart/bar_chart.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{App, Bounds, Hsla, Pixels, SharedString, TextAlign, Window, px};\nuse gpui_component_macros::IntoPlot;\nuse num_traits::{Num, ToPrimitive};\n\nuse crate::{\n    ActiveTheme,\n    plot::{\n        AXIS_GAP, Grid, Plot, PlotAxis,\n        label::Text,\n        scale::{Scale, ScaleBand, ScaleLinear, Sealed},\n        shape::Bar,\n    },\n};\n\nuse super::build_band_x_labels;\n\n#[derive(IntoPlot)]\npub struct BarChart<T, X, Y>\nwhere\n    T: 'static,\n    X: PartialEq + Into<SharedString> + 'static,\n    Y: Copy + PartialOrd + Num + ToPrimitive + Sealed + 'static,\n{\n    data: Vec<T>,\n    x: Option<Rc<dyn Fn(&T) -> X>>,\n    y: Option<Rc<dyn Fn(&T) -> Y>>,\n    fill: Option<Rc<dyn Fn(&T) -> Hsla>>,\n    tick_margin: usize,\n    label: Option<Rc<dyn Fn(&T) -> SharedString>>,\n    x_axis: bool,\n    grid: bool,\n}\n\nimpl<T, X, Y> BarChart<T, X, Y>\nwhere\n    X: PartialEq + Into<SharedString> + 'static,\n    Y: Copy + PartialOrd + Num + ToPrimitive + Sealed + 'static,\n{\n    pub fn new<I>(data: I) -> Self\n    where\n        I: IntoIterator<Item = T>,\n    {\n        Self {\n            data: data.into_iter().collect(),\n            x: None,\n            y: None,\n            fill: None,\n            tick_margin: 1,\n            label: None,\n            x_axis: true,\n            grid: true,\n        }\n    }\n\n    pub fn x(mut self, x: impl Fn(&T) -> X + 'static) -> Self {\n        self.x = Some(Rc::new(x));\n        self\n    }\n\n    pub fn y(mut self, y: impl Fn(&T) -> Y + 'static) -> Self {\n        self.y = Some(Rc::new(y));\n        self\n    }\n\n    pub fn fill<H>(mut self, fill: impl Fn(&T) -> H + 'static) -> Self\n    where\n        H: Into<Hsla> + 'static,\n    {\n        self.fill = Some(Rc::new(move |t| fill(t).into()));\n        self\n    }\n\n    pub fn tick_margin(mut self, tick_margin: usize) -> Self {\n        self.tick_margin = tick_margin;\n        self\n    }\n\n    pub fn label<S>(mut self, label: impl Fn(&T) -> S + 'static) -> Self\n    where\n        S: Into<SharedString> + 'static,\n    {\n        self.label = Some(Rc::new(move |t| label(t).into()));\n        self\n    }\n\n    /// Show or hide the x-axis line and labels.\n    ///\n    /// Default is true.\n    pub fn x_axis(mut self, x_axis: bool) -> Self {\n        self.x_axis = x_axis;\n        self\n    }\n\n    pub fn grid(mut self, grid: bool) -> Self {\n        self.grid = grid;\n        self\n    }\n}\n\nimpl<T, X, Y> Plot for BarChart<T, X, Y>\nwhere\n    X: PartialEq + Into<SharedString> + 'static,\n    Y: Copy + PartialOrd + Num + ToPrimitive + Sealed + 'static,\n{\n    fn paint(&mut self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App) {\n        let (Some(x_fn), Some(y_fn)) = (self.x.as_ref(), self.y.as_ref()) else {\n            return;\n        };\n\n        let width = bounds.size.width.as_f32();\n        let axis_gap = if self.x_axis { AXIS_GAP } else { 0. };\n        let height = bounds.size.height.as_f32() - axis_gap;\n\n        // X scale\n        let x = ScaleBand::new(self.data.iter().map(|v| x_fn(v)).collect(), vec![0., width])\n            .padding_inner(0.4)\n            .padding_outer(0.2);\n        let band_width = x.band_width();\n\n        // Y scale, ensure start from 0.\n        let y = ScaleLinear::new(\n            self.data\n                .iter()\n                .map(|v| y_fn(v))\n                .chain(Some(Y::zero()))\n                .collect(),\n            vec![height, 10.],\n        );\n\n        // Draw X axis\n        let mut axis = PlotAxis::new().stroke(cx.theme().border);\n        if self.x_axis {\n            let labels = build_band_x_labels(\n                &self.data,\n                x_fn.as_ref(),\n                &x,\n                band_width,\n                self.tick_margin,\n                cx.theme().muted_foreground,\n            );\n            axis = axis.x(height).x_label(labels);\n        }\n        axis.paint(&bounds, window, cx);\n\n        // Draw grid\n        if self.grid {\n            Grid::new()\n                .y((0..=3).map(|i| height * i as f32 / 4.0).collect())\n                .stroke(cx.theme().border)\n                .dash_array(&[px(4.), px(2.)])\n                .paint(&bounds, window);\n        }\n\n        // Draw bars\n        let x_fn = x_fn.clone();\n        let y_fn = y_fn.clone();\n        let default_fill = cx.theme().chart_2;\n        let fill = self.fill.clone();\n        let label_color = cx.theme().foreground;\n        let mut bar = Bar::new()\n            .data(&self.data)\n            .band_width(band_width)\n            .x(move |d| x.tick(&x_fn(d)))\n            .y0(move |_| height)\n            .y1(move |d| y.tick(&y_fn(d)))\n            .fill(move |d| fill.as_ref().map(|f| f(d)).unwrap_or(default_fill));\n\n        if let Some(label) = self.label.as_ref() {\n            let label = label.clone();\n            bar = bar.label(move |d, p| {\n                vec![Text::new(label(d), p, label_color).align(TextAlign::Center)]\n            });\n        }\n\n        bar.paint(&bounds, window, cx);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/chart/candlestick_chart.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{App, Bounds, Hsla, PathBuilder, Pixels, SharedString, Window, fill, px};\nuse gpui_component_macros::IntoPlot;\nuse num_traits::{Num, ToPrimitive};\n\nuse crate::{\n    ActiveTheme,\n    plot::{\n        AXIS_GAP, Grid, Plot, PlotAxis, origin_point,\n        scale::{Scale, ScaleBand, ScaleLinear, Sealed},\n    },\n};\n\nuse super::build_band_x_labels;\n\n#[derive(IntoPlot)]\npub struct CandlestickChart<T, X, Y>\nwhere\n    T: 'static,\n    X: PartialEq + Into<SharedString> + 'static,\n    Y: Copy + PartialOrd + Num + ToPrimitive + Sealed + 'static,\n{\n    data: Vec<T>,\n    x: Option<Rc<dyn Fn(&T) -> X>>,\n    open: Option<Rc<dyn Fn(&T) -> Y>>,\n    high: Option<Rc<dyn Fn(&T) -> Y>>,\n    low: Option<Rc<dyn Fn(&T) -> Y>>,\n    close: Option<Rc<dyn Fn(&T) -> Y>>,\n    tick_margin: usize,\n    body_width_ratio: f32,\n    x_axis: bool,\n    grid: bool,\n}\n\nimpl<T, X, Y> CandlestickChart<T, X, Y>\nwhere\n    X: PartialEq + Into<SharedString> + 'static,\n    Y: Copy + PartialOrd + Num + ToPrimitive + Sealed + 'static,\n{\n    pub fn new<I>(data: I) -> Self\n    where\n        I: IntoIterator<Item = T>,\n    {\n        Self {\n            data: data.into_iter().collect(),\n            x: None,\n            open: None,\n            high: None,\n            low: None,\n            close: None,\n            tick_margin: 1,\n            body_width_ratio: 0.8,\n            x_axis: true,\n            grid: true,\n        }\n    }\n\n    pub fn x(mut self, x: impl Fn(&T) -> X + 'static) -> Self {\n        self.x = Some(Rc::new(x));\n        self\n    }\n\n    pub fn open(mut self, open: impl Fn(&T) -> Y + 'static) -> Self {\n        self.open = Some(Rc::new(open));\n        self\n    }\n\n    pub fn high(mut self, high: impl Fn(&T) -> Y + 'static) -> Self {\n        self.high = Some(Rc::new(high));\n        self\n    }\n\n    pub fn low(mut self, low: impl Fn(&T) -> Y + 'static) -> Self {\n        self.low = Some(Rc::new(low));\n        self\n    }\n\n    pub fn close(mut self, close: impl Fn(&T) -> Y + 'static) -> Self {\n        self.close = Some(Rc::new(close));\n        self\n    }\n\n    pub fn tick_margin(mut self, tick_margin: usize) -> Self {\n        self.tick_margin = tick_margin;\n        self\n    }\n\n    pub fn body_width_ratio(mut self, ratio: f32) -> Self {\n        self.body_width_ratio = ratio;\n        self\n    }\n\n    /// Show or hide the x-axis line and labels.\n    ///\n    /// Default is true.\n    pub fn x_axis(mut self, x_axis: bool) -> Self {\n        self.x_axis = x_axis;\n        self\n    }\n\n    pub fn grid(mut self, grid: bool) -> Self {\n        self.grid = grid;\n        self\n    }\n}\n\nimpl<T, X, Y> Plot for CandlestickChart<T, X, Y>\nwhere\n    X: PartialEq + Into<SharedString> + 'static,\n    Y: Copy + PartialOrd + Num + ToPrimitive + Sealed + 'static,\n{\n    fn paint(&mut self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App) {\n        let (Some(x_fn), Some(open_fn), Some(high_fn), Some(low_fn), Some(close_fn)) = (\n            self.x.as_ref(),\n            self.open.as_ref(),\n            self.high.as_ref(),\n            self.low.as_ref(),\n            self.close.as_ref(),\n        ) else {\n            return;\n        };\n\n        let width = bounds.size.width.as_f32();\n        let axis_gap = if self.x_axis { AXIS_GAP } else { 0. };\n        let height = bounds.size.height.as_f32() - axis_gap;\n\n        // X scale\n        let x = ScaleBand::new(self.data.iter().map(|v| x_fn(v)).collect(), vec![0., width])\n            .padding_inner(0.4)\n            .padding_outer(0.2);\n        let band_width = x.band_width();\n\n        // Y scale\n        let all_values: Vec<Y> = self\n            .data\n            .iter()\n            .flat_map(|d| vec![high_fn(d), low_fn(d), open_fn(d), close_fn(d)])\n            .collect();\n        let y = ScaleLinear::new(all_values, vec![height, 10.]);\n\n        // Draw X axis\n        let mut axis = PlotAxis::new().stroke(cx.theme().border);\n        if self.x_axis {\n            let labels = build_band_x_labels(\n                &self.data,\n                x_fn.as_ref(),\n                &x,\n                band_width,\n                self.tick_margin,\n                cx.theme().muted_foreground,\n            );\n            axis = axis.x(height).x_label(labels);\n        }\n        axis.paint(&bounds, window, cx);\n\n        // Draw grid\n        if self.grid {\n            Grid::new()\n                .y((0..=3).map(|i| height * i as f32 / 4.0).collect())\n                .stroke(cx.theme().border)\n                .dash_array(&[px(4.), px(2.)])\n                .paint(&bounds, window);\n        }\n\n        // Draw candlesticks\n        let origin = bounds.origin;\n        let x_fn = x_fn.clone();\n        let open_fn = open_fn.clone();\n        let high_fn = high_fn.clone();\n        let low_fn = low_fn.clone();\n        let close_fn = close_fn.clone();\n\n        for d in &self.data {\n            let x_tick = x.tick(&x_fn(d));\n            let Some(x_tick) = x_tick else {\n                continue;\n            };\n\n            // Get OHLC values for the current data point\n            let open = open_fn(d);\n            let high = high_fn(d);\n            let low = low_fn(d);\n            let close = close_fn(d);\n\n            // Convert values to pixel coordinates\n            let open_y = y.tick(&open);\n            let high_y = y.tick(&high);\n            let low_y = y.tick(&low);\n            let close_y = y.tick(&close);\n\n            let (Some(open_y), Some(high_y), Some(low_y), Some(close_y)) =\n                (open_y, high_y, low_y, close_y)\n            else {\n                continue;\n            };\n\n            // Determine if bullish (close > open) or bearish (close < open)\n            let is_bullish = close > open;\n            let color: Hsla = if is_bullish {\n                cx.theme().chart_bullish\n            } else {\n                cx.theme().chart_bearish\n            };\n\n            // Calculate candlestick body dimensions\n            let center_x = x_tick + band_width / 2.;\n            let body_width = band_width * self.body_width_ratio;\n            let body_left = center_x - body_width / 2.;\n            let body_right = center_x + body_width / 2.;\n\n            // Draw wick (high to low line)\n            let mut wick_builder = PathBuilder::stroke(px(1.));\n            wick_builder.move_to(origin_point(px(center_x), px(high_y), origin));\n            wick_builder.line_to(origin_point(px(center_x), px(low_y), origin));\n\n            if let Ok(path) = wick_builder.build() {\n                window.paint_path(path, color);\n            }\n\n            // Draw body (open to close rectangle)\n            // For bullish: top is close, bottom is open\n            // For bearish: top is open, bottom is close\n            let (top, bottom) = if is_bullish {\n                (close_y, open_y)\n            } else {\n                (open_y, close_y)\n            };\n\n            let body_bounds = Bounds::from_corners(\n                origin_point(px(body_left), px(top), origin),\n                origin_point(px(body_right), px(bottom), origin),\n            );\n\n            window.paint_quad(fill(body_bounds, color));\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/chart/line_chart.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{App, Bounds, Hsla, Pixels, SharedString, Window, px};\nuse gpui_component_macros::IntoPlot;\nuse num_traits::{Num, ToPrimitive};\n\nuse crate::{\n    ActiveTheme,\n    plot::{\n        AXIS_GAP, Grid, Plot, PlotAxis, StrokeStyle,\n        scale::{Scale, ScaleLinear, ScalePoint, Sealed},\n        shape::Line,\n    },\n};\n\nuse super::build_point_x_labels;\n\n#[derive(IntoPlot)]\npub struct LineChart<T, X, Y>\nwhere\n    T: 'static,\n    X: PartialEq + Into<SharedString> + 'static,\n    Y: Copy + PartialOrd + Num + ToPrimitive + Sealed + 'static,\n{\n    data: Vec<T>,\n    x: Option<Rc<dyn Fn(&T) -> X>>,\n    y: Option<Rc<dyn Fn(&T) -> Y>>,\n    stroke: Option<Hsla>,\n    stroke_style: StrokeStyle,\n    dot: bool,\n    tick_margin: usize,\n    x_axis: bool,\n    grid: bool,\n}\n\nimpl<T, X, Y> LineChart<T, X, Y>\nwhere\n    X: PartialEq + Into<SharedString> + 'static,\n    Y: Copy + PartialOrd + Num + ToPrimitive + Sealed + 'static,\n{\n    pub fn new<I>(data: I) -> Self\n    where\n        I: IntoIterator<Item = T>,\n    {\n        Self {\n            data: data.into_iter().collect(),\n            stroke: None,\n            stroke_style: Default::default(),\n            dot: false,\n            x: None,\n            y: None,\n            tick_margin: 1,\n            x_axis: true,\n            grid: true,\n        }\n    }\n\n    pub fn x(mut self, x: impl Fn(&T) -> X + 'static) -> Self {\n        self.x = Some(Rc::new(x));\n        self\n    }\n\n    pub fn y(mut self, y: impl Fn(&T) -> Y + 'static) -> Self {\n        self.y = Some(Rc::new(y));\n        self\n    }\n\n    pub fn stroke(mut self, stroke: impl Into<Hsla>) -> Self {\n        self.stroke = Some(stroke.into());\n        self\n    }\n\n    pub fn natural(mut self) -> Self {\n        self.stroke_style = StrokeStyle::Natural;\n        self\n    }\n\n    pub fn linear(mut self) -> Self {\n        self.stroke_style = StrokeStyle::Linear;\n        self\n    }\n\n    pub fn step_after(mut self) -> Self {\n        self.stroke_style = StrokeStyle::StepAfter;\n        self\n    }\n\n    pub fn dot(mut self) -> Self {\n        self.dot = true;\n        self\n    }\n\n    pub fn tick_margin(mut self, tick_margin: usize) -> Self {\n        self.tick_margin = tick_margin;\n        self\n    }\n\n    /// Show or hide the x-axis line and labels.\n    ///\n    /// Default is true.\n    pub fn x_axis(mut self, x_axis: bool) -> Self {\n        self.x_axis = x_axis;\n        self\n    }\n\n    pub fn grid(mut self, grid: bool) -> Self {\n        self.grid = grid;\n        self\n    }\n}\n\nimpl<T, X, Y> Plot for LineChart<T, X, Y>\nwhere\n    X: PartialEq + Into<SharedString> + 'static,\n    Y: Copy + PartialOrd + Num + ToPrimitive + Sealed + 'static,\n{\n    fn paint(&mut self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App) {\n        let (Some(x_fn), Some(y_fn)) = (self.x.as_ref(), self.y.as_ref()) else {\n            return;\n        };\n\n        let width = bounds.size.width.as_f32();\n        let axis_gap = if self.x_axis { AXIS_GAP } else { 0. };\n        let height = bounds.size.height.as_f32() - axis_gap;\n\n        // X scale\n        let x = ScalePoint::new(self.data.iter().map(|v| x_fn(v)).collect(), vec![0., width]);\n\n        // Y scale, ensure start from 0.\n        let y = ScaleLinear::new(\n            self.data\n                .iter()\n                .map(|v| y_fn(v))\n                .chain(Some(Y::zero()))\n                .collect(),\n            vec![height, 10.],\n        );\n\n        // Draw X axis\n        let mut axis = PlotAxis::new().stroke(cx.theme().border);\n        if self.x_axis {\n            let labels = build_point_x_labels(\n                &self.data,\n                x_fn.as_ref(),\n                &x,\n                self.tick_margin,\n                cx.theme().muted_foreground,\n            );\n            axis = axis.x(height).x_label(labels);\n        }\n        axis.paint(&bounds, window, cx);\n\n        // Draw grid\n        if self.grid {\n            Grid::new()\n                .y((0..=3).map(|i| height * i as f32 / 4.0).collect())\n                .stroke(cx.theme().border)\n                .dash_array(&[px(4.), px(2.)])\n                .paint(&bounds, window);\n        }\n\n        // Draw line\n        let stroke = self.stroke.unwrap_or(cx.theme().chart_2);\n        let x_fn = x_fn.clone();\n        let y_fn = y_fn.clone();\n        let mut line = Line::new()\n            .data(&self.data)\n            .x(move |d| x.tick(&x_fn(d)))\n            .y(move |d| y.tick(&y_fn(d)))\n            .stroke(stroke)\n            .stroke_style(self.stroke_style)\n            .stroke_width(2.);\n\n        if self.dot {\n            line = line.dot().dot_size(8.).dot_fill_color(stroke);\n        }\n\n        line.paint(&bounds, window);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/chart/mod.rs",
    "content": "mod area_chart;\nmod bar_chart;\nmod candlestick_chart;\nmod line_chart;\nmod pie_chart;\n\npub use area_chart::AreaChart;\npub use bar_chart::BarChart;\npub use candlestick_chart::CandlestickChart;\npub use line_chart::LineChart;\npub use pie_chart::PieChart;\n\nuse gpui::{Hsla, SharedString, TextAlign};\n\nuse crate::plot::{\n    AxisText,\n    scale::{Scale, ScaleBand, ScalePoint},\n};\n\n/// Build x-axis labels for point-based scales (`LineChart`, `AreaChart`).\n///\n/// Point scales place items at evenly spaced positions. The first label is\n/// left-aligned, the last is right-aligned, and the rest are centered.\npub(crate) fn build_point_x_labels<T, X>(\n    data: &[T],\n    x_fn: &dyn Fn(&T) -> X,\n    x_scale: &ScalePoint<X>,\n    tick_margin: usize,\n    color: Hsla,\n) -> Vec<AxisText>\nwhere\n    X: PartialEq + Into<SharedString>,\n{\n    let data_len = data.len();\n    data.iter()\n        .enumerate()\n        .filter_map(|(i, d)| {\n            if (i + 1) % tick_margin != 0 {\n                return None;\n            }\n            x_scale.tick(&x_fn(d)).map(|x_tick| {\n                let align = match i {\n                    0 if data_len == 1 => TextAlign::Center,\n                    0 => TextAlign::Left,\n                    i if i == data_len - 1 => TextAlign::Right,\n                    _ => TextAlign::Center,\n                };\n                // Call x_fn again to get an owned value for the label text.\n                AxisText::new(x_fn(d).into(), x_tick, color).align(align)\n            })\n        })\n        .collect()\n}\n\n/// Build x-axis labels for band-based scales (`BarChart`, `CandlestickChart`).\n///\n/// Band scales place items in evenly sized bands. Labels are always\n/// center-aligned within their band.\npub(crate) fn build_band_x_labels<T, X>(\n    data: &[T],\n    x_fn: &dyn Fn(&T) -> X,\n    x_scale: &ScaleBand<X>,\n    band_width: f32,\n    tick_margin: usize,\n    color: Hsla,\n) -> Vec<AxisText>\nwhere\n    X: PartialEq + Into<SharedString>,\n{\n    data.iter()\n        .enumerate()\n        .filter_map(|(i, d)| {\n            if (i + 1) % tick_margin != 0 {\n                return None;\n            }\n            x_scale.tick(&x_fn(d)).map(|x_tick| {\n                // Call x_fn again to get an owned value for the label text.\n                AxisText::new(x_fn(d).into(), x_tick + band_width / 2., color)\n                    .align(TextAlign::Center)\n            })\n        })\n        .collect()\n}\n"
  },
  {
    "path": "crates/ui/src/chart/pie_chart.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{App, Bounds, Hsla, Pixels, Window};\nuse gpui_component_macros::IntoPlot;\nuse num_traits::Zero;\n\nuse crate::{\n    ActiveTheme,\n    plot::{\n        Plot,\n        shape::{Arc, ArcData, Pie},\n    },\n};\n\n#[derive(IntoPlot)]\npub struct PieChart<T: 'static> {\n    data: Vec<T>,\n    inner_radius: f32,\n    inner_radius_fn: Option<Rc<dyn Fn(&ArcData<T>) -> f32 + 'static>>,\n    outer_radius: f32,\n    outer_radius_fn: Option<Rc<dyn Fn(&ArcData<T>) -> f32 + 'static>>,\n    pad_angle: f32,\n    value: Option<Rc<dyn Fn(&T) -> f32>>,\n    color: Option<Rc<dyn Fn(&T) -> Hsla>>,\n}\n\nimpl<T> PieChart<T> {\n    pub fn new<I>(data: I) -> Self\n    where\n        I: IntoIterator<Item = T>,\n    {\n        Self {\n            data: data.into_iter().collect(),\n            inner_radius: 0.,\n            inner_radius_fn: None,\n            outer_radius: 0.,\n            outer_radius_fn: None,\n            pad_angle: 0.,\n            value: None,\n            color: None,\n        }\n    }\n\n    /// Set the inner radius of the pie chart.\n    pub fn inner_radius(mut self, inner_radius: f32) -> Self {\n        self.inner_radius = inner_radius;\n        self\n    }\n\n    /// Set the inner radius of the pie chart based on the arc data.\n    pub fn inner_radius_fn(\n        mut self,\n        inner_radius_fn: impl Fn(&ArcData<T>) -> f32 + 'static,\n    ) -> Self {\n        self.inner_radius_fn = Some(Rc::new(inner_radius_fn));\n        self\n    }\n\n    fn get_inner_radius(&self, arc: &ArcData<T>) -> f32 {\n        if let Some(inner_radius_fn) = self.inner_radius_fn.as_ref() {\n            inner_radius_fn(arc)\n        } else {\n            self.inner_radius\n        }\n    }\n\n    /// Set the outer radius of the pie chart.\n    pub fn outer_radius(mut self, outer_radius: f32) -> Self {\n        self.outer_radius = outer_radius;\n        self\n    }\n\n    /// Set the outer radius of the pie chart based on the arc data.\n    pub fn outer_radius_fn(\n        mut self,\n        outer_radius_fn: impl Fn(&ArcData<T>) -> f32 + 'static,\n    ) -> Self {\n        self.outer_radius_fn = Some(Rc::new(outer_radius_fn));\n        self\n    }\n\n    fn get_outer_radius(&self, arc: &ArcData<T>) -> f32 {\n        if let Some(outer_radius_fn) = self.outer_radius_fn.as_ref() {\n            outer_radius_fn(arc)\n        } else {\n            self.outer_radius\n        }\n    }\n\n    /// Set the pad angle of the pie chart.\n    pub fn pad_angle(mut self, pad_angle: f32) -> Self {\n        self.pad_angle = pad_angle;\n        self\n    }\n\n    pub fn value(mut self, value: impl Fn(&T) -> f32 + 'static) -> Self {\n        self.value = Some(Rc::new(value));\n        self\n    }\n\n    /// Set the color of the pie chart.\n    pub fn color<H>(mut self, color: impl Fn(&T) -> H + 'static) -> Self\n    where\n        H: Into<Hsla> + 'static,\n    {\n        self.color = Some(Rc::new(move |t| color(t).into()));\n        self\n    }\n}\n\nimpl<T> Plot for PieChart<T> {\n    fn paint(&mut self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App) {\n        let Some(value_fn) = self.value.as_ref() else {\n            return;\n        };\n\n        let outer_radius = if self.outer_radius.is_zero() {\n            bounds.size.height.as_f32() * 0.4\n        } else {\n            self.outer_radius\n        };\n\n        let arc = Arc::new()\n            .inner_radius(self.inner_radius)\n            .outer_radius(outer_radius);\n        let value_fn = value_fn.clone();\n        let mut pie = Pie::<T>::new().value(move |d| Some(value_fn(d)));\n        pie = pie.pad_angle(self.pad_angle);\n        let arcs = pie.arcs(&self.data);\n\n        for a in &arcs {\n            let inner_radius = self.get_inner_radius(a);\n            let outer_radius = self.get_outer_radius(a);\n            arc.paint(\n                a,\n                if let Some(color_fn) = self.color.as_ref() {\n                    color_fn(a.data)\n                } else {\n                    cx.theme().chart_2\n                },\n                Some(inner_radius),\n                Some(outer_radius),\n                &bounds,\n                window,\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/checkbox.rs",
    "content": "use std::{rc::Rc, time::Duration};\n\nuse crate::{\n    ActiveTheme, Disableable, FocusableExt, IconName, Selectable, Sizable, Size, StyledExt as _,\n    icon::IconNamed, text::Text, v_flex,\n};\nuse gpui::{\n    Animation, AnimationExt, AnyElement, App, Div, ElementId, InteractiveElement, IntoElement,\n    ParentElement, RenderOnce, StatefulInteractiveElement, StyleRefinement, Styled, Window, div,\n    prelude::FluentBuilder as _, px, relative, rems, svg,\n};\n\n/// A Checkbox element.\n#[derive(IntoElement)]\npub struct Checkbox {\n    id: ElementId,\n    base: Div,\n    style: StyleRefinement,\n    label: Option<Text>,\n    children: Vec<AnyElement>,\n    checked: bool,\n    disabled: bool,\n    size: Size,\n    tab_stop: bool,\n    tab_index: isize,\n    on_click: Option<Rc<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,\n}\n\nimpl Checkbox {\n    /// Create a new Checkbox with the given id.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            base: div(),\n            style: StyleRefinement::default(),\n            label: None,\n            children: Vec::new(),\n            checked: false,\n            disabled: false,\n            size: Size::default(),\n            on_click: None,\n            tab_stop: true,\n            tab_index: 0,\n        }\n    }\n\n    /// Set the label for the checkbox.\n    pub fn label(mut self, label: impl Into<Text>) -> Self {\n        self.label = Some(label.into());\n        self\n    }\n\n    /// Set the checked state for the checkbox.\n    pub fn checked(mut self, checked: bool) -> Self {\n        self.checked = checked;\n        self\n    }\n\n    /// Set the click handler for the checkbox.\n    ///\n    /// The `&bool` parameter indicates the new checked state after the click.\n    pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {\n        self.on_click = Some(Rc::new(handler));\n        self\n    }\n\n    /// Set the tab stop for the checkbox, default is true.\n    pub fn tab_stop(mut self, tab_stop: bool) -> Self {\n        self.tab_stop = tab_stop;\n        self\n    }\n\n    /// Set the tab index for the checkbox, default is 0.\n    pub fn tab_index(mut self, tab_index: isize) -> Self {\n        self.tab_index = tab_index;\n        self\n    }\n\n    fn handle_click(\n        on_click: &Option<Rc<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,\n        checked: bool,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        let new_checked = !checked;\n        if let Some(f) = on_click {\n            (f)(&new_checked, window, cx);\n        }\n    }\n}\n\nimpl InteractiveElement for Checkbox {\n    fn interactivity(&mut self) -> &mut gpui::Interactivity {\n        self.base.interactivity()\n    }\n}\nimpl StatefulInteractiveElement for Checkbox {}\n\nimpl Styled for Checkbox {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Disableable for Checkbox {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl Selectable for Checkbox {\n    fn selected(self, selected: bool) -> Self {\n        self.checked(selected)\n    }\n\n    fn is_selected(&self) -> bool {\n        self.checked\n    }\n}\n\nimpl ParentElement for Checkbox {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Sizable for Checkbox {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\npub(crate) fn checkbox_check_icon(\n    id: ElementId,\n    size: Size,\n    checked: bool,\n    disabled: bool,\n    window: &mut Window,\n    cx: &mut App,\n) -> impl IntoElement {\n    let toggle_state = window.use_keyed_state(id, cx, |_, _| checked);\n    let color = if disabled {\n        cx.theme().primary_foreground.opacity(0.5)\n    } else {\n        cx.theme().primary_foreground\n    };\n\n    svg()\n        .absolute()\n        .top_px()\n        .left_px()\n        .map(|this| match size {\n            Size::XSmall => this.size_2(),\n            Size::Small => this.size_2p5(),\n            Size::Medium => this.size_3(),\n            Size::Large => this.size_3p5(),\n            _ => this.size_3(),\n        })\n        .text_color(color)\n        .map(|this| match checked {\n            true => this.path(IconName::Check.path()),\n            _ => this,\n        })\n        .map(|this| {\n            if !disabled && checked != *toggle_state.read(cx) {\n                let duration = Duration::from_secs_f64(0.25);\n                cx.spawn({\n                    let toggle_state = toggle_state.clone();\n                    async move |cx| {\n                        cx.background_executor().timer(duration).await;\n                        _ = toggle_state.update(cx, |this, _| *this = checked);\n                    }\n                })\n                .detach();\n\n                this.with_animation(\n                    ElementId::NamedInteger(\"toggle\".into(), checked as u64),\n                    Animation::new(Duration::from_secs_f64(0.25)),\n                    move |this, delta| {\n                        this.opacity(if checked { 1.0 * delta } else { 1.0 - delta })\n                    },\n                )\n                .into_any_element()\n            } else {\n                this.into_any_element()\n            }\n        })\n}\n\nimpl RenderOnce for Checkbox {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let checked = self.checked;\n\n        let focus_handle = window\n            .use_keyed_state(self.id.clone(), cx, |_, cx| cx.focus_handle())\n            .read(cx)\n            .clone();\n        let is_focused = focus_handle.is_focused(window);\n\n        let border_color = if checked {\n            cx.theme().primary\n        } else {\n            cx.theme().input\n        };\n        let color = if self.disabled {\n            border_color.opacity(0.5)\n        } else {\n            border_color\n        };\n        let radius = cx.theme().radius.min(px(4.));\n\n        div().child(\n            self.base\n                .id(self.id.clone())\n                .when(!self.disabled, |this| {\n                    this.track_focus(\n                        &focus_handle\n                            .tab_stop(self.tab_stop)\n                            .tab_index(self.tab_index),\n                    )\n                })\n                .h_flex()\n                .gap_2()\n                .items_start()\n                .line_height(relative(1.))\n                .text_color(cx.theme().foreground)\n                .map(|this| match self.size {\n                    Size::XSmall => this.text_xs(),\n                    Size::Small => this.text_sm(),\n                    Size::Medium => this.text_base(),\n                    Size::Large => this.text_lg(),\n                    _ => this,\n                })\n                .when(self.disabled, |this| {\n                    this.text_color(cx.theme().muted_foreground)\n                })\n                .rounded(cx.theme().radius * 0.5)\n                .focus_ring(is_focused, px(2.), window, cx)\n                .refine_style(&self.style)\n                .child(\n                    div()\n                        .relative()\n                        .map(|this| match self.size {\n                            Size::XSmall => this.size_3(),\n                            Size::Small => this.size_3p5(),\n                            Size::Medium => this.size_4(),\n                            Size::Large => this.size(rems(1.125)),\n                            _ => this.size_4(),\n                        })\n                        .flex_shrink_0()\n                        .border_1()\n                        .border_color(color)\n                        .rounded(radius)\n                        .when(cx.theme().shadow && !self.disabled, |this| this.shadow_xs())\n                        .map(|this| match checked {\n                            false => this.bg(cx.theme().input_background()),\n                            _ => this.bg(color),\n                        })\n                        .child(checkbox_check_icon(\n                            self.id,\n                            self.size,\n                            checked,\n                            self.disabled,\n                            window,\n                            cx,\n                        )),\n                )\n                .when(self.label.is_some() || !self.children.is_empty(), |this| {\n                    this.child(\n                        v_flex()\n                            .w_full()\n                            .line_height(relative(1.2))\n                            .gap_1()\n                            .map(|this| {\n                                if let Some(label) = self.label {\n                                    this.child(\n                                        div()\n                                            .size_full()\n                                            .text_color(cx.theme().foreground)\n                                            .when(self.disabled, |this| {\n                                                this.text_color(cx.theme().muted_foreground)\n                                            })\n                                            .line_height(relative(1.))\n                                            .child(label),\n                                    )\n                                } else {\n                                    this\n                                }\n                            })\n                            .children(self.children),\n                    )\n                })\n                .on_mouse_down(gpui::MouseButton::Left, |_, window, _| {\n                    // Avoid focus on mouse down.\n                    window.prevent_default();\n                })\n                .when(!self.disabled, |this| {\n                    this.on_click({\n                        let on_click = self.on_click.clone();\n                        move |_, window, cx| {\n                            window.prevent_default();\n                            Self::handle_click(&on_click, checked, window, cx);\n                        }\n                    })\n                }),\n        )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/clipboard.rs",
    "content": "use std::{rc::Rc, time::Duration};\n\nuse gpui::{\n    prelude::FluentBuilder, App, ClipboardItem, ElementId, IntoElement, RenderOnce, SharedString,\n    Window,\n};\n\nuse crate::{\n    button::{Button, ButtonVariants as _},\n    IconName, Sizable as _,\n};\n\n/// An element that provides clipboard copy functionality.\n#[derive(IntoElement)]\npub struct Clipboard {\n    id: ElementId,\n    value: SharedString,\n    value_fn: Option<Rc<dyn Fn(&mut Window, &mut App) -> SharedString>>,\n    on_copied: Option<Rc<dyn Fn(SharedString, &mut Window, &mut App)>>,\n}\n\nimpl Clipboard {\n    /// Create a new Clipboard element with the given ID.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            value: SharedString::default(),\n            value_fn: None,\n            on_copied: None,\n        }\n    }\n\n    /// Set the value for copying to the clipboard. Default is an empty string.\n    pub fn value(mut self, value: impl Into<SharedString>) -> Self {\n        self.value = value.into();\n        self\n    }\n\n    /// Set the value of the clipboard to the result of the given function. Default is None.\n    ///\n    /// When used this, the copy value will use the result of the function.\n    pub fn value_fn(\n        mut self,\n        value: impl Fn(&mut Window, &mut App) -> SharedString + 'static,\n    ) -> Self {\n        self.value_fn = Some(Rc::new(value));\n        self\n    }\n\n    /// Set a callback to be invoked when the content is copied to the clipboard.\n    pub fn on_copied<F>(mut self, handler: F) -> Self\n    where\n        F: Fn(SharedString, &mut Window, &mut App) + 'static,\n    {\n        self.on_copied = Some(Rc::new(handler));\n        self\n    }\n}\n\nimpl RenderOnce for Clipboard {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let state = window.use_keyed_state(self.id.clone(), cx, |_, _| ClipboardState::default());\n\n        let value = self.value.clone();\n        let clipboard_id = self.id.clone();\n        let copied = state.read(cx).copied;\n        let value_fn = self.value_fn.clone();\n\n        Button::new(clipboard_id)\n            .icon(if copied {\n                IconName::Check\n            } else {\n                IconName::Copy\n            })\n            .ghost()\n            .xsmall()\n            .when(!copied, |this| {\n                this.on_click({\n                    let state = state.clone();\n                    let on_copied = self.on_copied.clone();\n                    move |_, window, cx| {\n                        cx.stop_propagation();\n                        let value = value_fn\n                            .as_ref()\n                            .map(|f| f(window, cx))\n                            .unwrap_or_else(|| value.clone());\n                        cx.write_to_clipboard(ClipboardItem::new_string(value.to_string()));\n                        state.update(cx, |state, cx| {\n                            state.copied = true;\n                            cx.notify();\n                        });\n\n                        let state = state.clone();\n                        cx.spawn(async move |cx| {\n                            cx.background_executor().timer(Duration::from_secs(2)).await;\n                            _ = state.update(cx, |state, cx| {\n                                state.copied = false;\n                                cx.notify();\n                            });\n                        })\n                        .detach();\n\n                        if let Some(on_copied) = &on_copied {\n                            on_copied(value.clone(), window, cx);\n                        }\n                    }\n                })\n            })\n    }\n}\n\n#[doc(hidden)]\n#[derive(Default)]\nstruct ClipboardState {\n    copied: bool,\n}\n"
  },
  {
    "path": "crates/ui/src/collapsible.rs",
    "content": "use gpui::{\n    AnyElement, App, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, Window,\n};\n\nuse crate::{v_flex, StyledExt};\n\nenum CollapsibleChild {\n    Element(AnyElement),\n    Content(AnyElement),\n}\n\nimpl CollapsibleChild {\n    fn is_content(&self) -> bool {\n        matches!(self, CollapsibleChild::Content(_))\n    }\n}\n\n/// An interactive element which expands/collapses.\n#[derive(IntoElement)]\npub struct Collapsible {\n    style: StyleRefinement,\n    children: Vec<CollapsibleChild>,\n    open: bool,\n}\n\nimpl Collapsible {\n    /// Creates a new `Collapsible` instance.\n    pub fn new() -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            open: false,\n            children: vec![],\n        }\n    }\n\n    /// Sets whether the collapsible is open. default is false.\n    pub fn open(mut self, open: bool) -> Self {\n        self.open = open;\n        self\n    }\n\n    /// Sets the content of the collapsible.\n    ///\n    /// If `open` is false, content will be hidden.\n    pub fn content(mut self, content: impl IntoElement) -> Self {\n        self.children\n            .push(CollapsibleChild::Content(content.into_any_element()));\n        self\n    }\n}\n\nimpl Styled for Collapsible {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl ParentElement for Collapsible {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children\n            .extend(elements.into_iter().map(|el| CollapsibleChild::Element(el)));\n    }\n}\n\nimpl RenderOnce for Collapsible {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        v_flex()\n            .refine_style(&self.style)\n            .children(self.children.into_iter().filter_map(|child| {\n                if child.is_content() && !self.open {\n                    None\n                } else {\n                    match child {\n                        CollapsibleChild::Element(el) => Some(el),\n                        CollapsibleChild::Content(el) => Some(el),\n                    }\n                }\n            }))\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/color_picker.rs",
    "content": "use gpui::{\n    App, AppContext, Context, Corner, Div, ElementId, Entity, EventEmitter, FocusHandle, Focusable,\n    Hsla, InteractiveElement as _, IntoElement, KeyBinding, ParentElement, Render, RenderOnce,\n    SharedString, Stateful, StatefulInteractiveElement as _, StyleRefinement, Styled, Subscription,\n    TextAlign, Window, div, hsla, linear_color_stop, linear_gradient, prelude::FluentBuilder as _,\n};\nuse rust_i18n::t;\n\nuse crate::{\n    ActiveTheme as _, Colorize as _, Icon, Selectable, Sizable, Size, StyleSized,\n    actions::Confirm,\n    divider::Divider,\n    h_flex,\n    input::{Input, InputEvent, InputState},\n    popover::Popover,\n    slider::{Slider, SliderEvent, SliderState},\n    tab::{Tab, TabBar},\n    tooltip::Tooltip,\n    v_flex,\n};\n\nconst CONTEXT: &'static str = \"ColorPicker\";\npub(crate) fn init(cx: &mut App) {\n    cx.bind_keys([KeyBinding::new(\n        \"enter\",\n        Confirm { secondary: false },\n        Some(CONTEXT),\n    )])\n}\n\n/// Events emitted by the [`ColorPicker`].\n#[derive(Clone)]\npub enum ColorPickerEvent {\n    Change(Option<Hsla>),\n}\n\nfn color_palettes() -> Vec<Vec<Hsla>> {\n    use crate::theme::DEFAULT_COLORS;\n    use itertools::Itertools as _;\n\n    macro_rules! c {\n        ($color:tt) => {\n            DEFAULT_COLORS\n                .$color\n                .keys()\n                .sorted()\n                .map(|k| DEFAULT_COLORS.$color.get(k).map(|c| c.hsla).unwrap())\n                .collect::<Vec<_>>()\n        };\n    }\n\n    vec![\n        c!(stone),\n        c!(red),\n        c!(orange),\n        c!(yellow),\n        c!(green),\n        c!(cyan),\n        c!(blue),\n        c!(purple),\n        c!(pink),\n    ]\n}\n\n#[derive(Clone)]\nstruct HslaSliders {\n    hue: Entity<SliderState>,\n    saturation: Entity<SliderState>,\n    lightness: Entity<SliderState>,\n    alpha: Entity<SliderState>,\n}\n\nimpl HslaSliders {\n    fn new(cx: &mut App) -> Self {\n        Self {\n            hue: cx.new(|_| {\n                SliderState::new()\n                    .min(0.)\n                    .max(1.)\n                    .step(0.01)\n                    .default_value(0.)\n            }),\n            saturation: cx.new(|_| {\n                SliderState::new()\n                    .min(0.)\n                    .max(1.)\n                    .step(0.01)\n                    .default_value(0.)\n            }),\n            lightness: cx.new(|_| {\n                SliderState::new()\n                    .min(0.)\n                    .max(1.)\n                    .step(0.01)\n                    .default_value(0.)\n            }),\n            alpha: cx.new(|_| {\n                SliderState::new()\n                    .min(0.)\n                    .max(1.)\n                    .step(0.01)\n                    .default_value(0.)\n            }),\n        }\n    }\n\n    fn read(&self, cx: &App) -> Hsla {\n        hsla(\n            self.hue.read(cx).value().start(),\n            self.saturation.read(cx).value().start(),\n            self.lightness.read(cx).value().start(),\n            self.alpha.read(cx).value().start(),\n        )\n    }\n\n    fn update(&self, new_color: Hsla, window: &mut Window, cx: &mut App) {\n        self.hue.update(cx, |slider, cx| {\n            slider.set_value(new_color.h, window, cx);\n        });\n        self.saturation.update(cx, |slider, cx| {\n            slider.set_value(new_color.s, window, cx);\n        });\n        self.lightness.update(cx, |slider, cx| {\n            slider.set_value(new_color.l, window, cx);\n        });\n        self.alpha.update(cx, |slider, cx| {\n            slider.set_value(new_color.a, window, cx);\n        });\n    }\n}\n\n/// State of the [`ColorPicker`].\npub struct ColorPickerState {\n    focus_handle: FocusHandle,\n    value: Option<Hsla>,\n    hovered_color: Option<Hsla>,\n    state: Entity<InputState>,\n    hsla_sliders: HslaSliders,\n    needs_slider_sync: bool,\n    suppress_input_change: bool,\n    active_tab: usize,\n    open: bool,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl ColorPickerState {\n    /// Create a new [`ColorPickerState`].\n    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let state = cx.new(|cx| {\n            InputState::new(window, cx).pattern(regex::Regex::new(r\"^#[0-9a-fA-F]{0,8}$\").unwrap())\n        });\n        let hsla_sliders = HslaSliders::new(cx);\n\n        let mut _subscriptions = vec![\n            cx.subscribe_in(\n                &state,\n                window,\n                |this, state, ev: &InputEvent, window, cx| match ev {\n                    InputEvent::Change => {\n                        if this.suppress_input_change {\n                            return;\n                        }\n                        let value = state.read(cx).value();\n                        if let Ok(color) = Hsla::parse_hex(value.as_str()) {\n                            this.hovered_color = Some(color);\n                            this.sync_sliders(Some(color), window, cx);\n                        }\n                    }\n                    InputEvent::PressEnter { .. } => {\n                        let val = this.state.read(cx).value();\n                        if let Ok(color) = Hsla::parse_hex(&val) {\n                            this.open = false;\n                            this.update_value(Some(color), true, window, cx);\n                        }\n                    }\n                    _ => {}\n                },\n            ),\n            cx.subscribe_in(\n                &hsla_sliders.hue,\n                window,\n                |this, _, _: &SliderEvent, window, cx| {\n                    let color = this.hsla_sliders.read(cx);\n                    this.update_value_from_slider(color, true, window, cx);\n                },\n            ),\n            cx.subscribe_in(\n                &hsla_sliders.saturation,\n                window,\n                |this, _, _: &SliderEvent, window, cx| {\n                    let color = this.hsla_sliders.read(cx);\n                    this.update_value_from_slider(color, true, window, cx);\n                },\n            ),\n            cx.subscribe_in(\n                &hsla_sliders.lightness,\n                window,\n                |this, _, _: &SliderEvent, window, cx| {\n                    let color = this.hsla_sliders.read(cx);\n                    this.update_value_from_slider(color, true, window, cx);\n                },\n            ),\n            cx.subscribe_in(\n                &hsla_sliders.alpha,\n                window,\n                |this, _, _: &SliderEvent, window, cx| {\n                    let color = this.hsla_sliders.read(cx);\n                    this.update_value_from_slider(color, true, window, cx);\n                },\n            ),\n        ];\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            value: None,\n            hovered_color: None,\n            state,\n            hsla_sliders,\n            needs_slider_sync: false,\n            suppress_input_change: false,\n            active_tab: 0,\n            open: false,\n            _subscriptions,\n        }\n    }\n\n    /// Set default color value.\n    pub fn default_value(mut self, value: impl Into<Hsla>) -> Self {\n        let value = value.into();\n        self.value = Some(value);\n        self.hovered_color = Some(value);\n        self.needs_slider_sync = true;\n        self\n    }\n\n    /// Set current color value.\n    pub fn set_value(\n        &mut self,\n        value: impl Into<Hsla>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.update_value(Some(value.into()), false, window, cx)\n    }\n\n    /// Get current color value.\n    pub fn value(&self) -> Option<Hsla> {\n        self.value\n    }\n\n    fn on_confirm(&mut self, _: &Confirm, _: &mut Window, cx: &mut Context<Self>) {\n        self.open = !self.open;\n        cx.notify();\n    }\n\n    fn update_value(\n        &mut self,\n        value: Option<Hsla>,\n        emit: bool,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.needs_slider_sync = false;\n        self.value = value;\n        self.hovered_color = value;\n        self.state.update(cx, |view, cx| {\n            if let Some(value) = value {\n                view.set_value(value.to_hex(), window, cx);\n            } else {\n                view.set_value(\"\", window, cx);\n            }\n        });\n        if emit {\n            cx.emit(ColorPickerEvent::Change(value));\n        }\n        cx.notify();\n    }\n\n    fn update_value_from_slider(\n        &mut self,\n        value: Hsla,\n        emit: bool,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.needs_slider_sync = false;\n        self.value = Some(value);\n        self.hovered_color = Some(value);\n        if emit {\n            cx.emit(ColorPickerEvent::Change(Some(value)));\n        }\n        cx.notify();\n    }\n\n    fn sync_sliders(&mut self, color: Option<Hsla>, window: &mut Window, cx: &mut Context<Self>) {\n        if let Some(color) = color {\n            self.hsla_sliders.update(color, window, cx);\n        }\n    }\n}\n\nimpl EventEmitter<ColorPickerEvent> for ColorPickerState {}\n\nimpl Render for ColorPickerState {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        self.state.clone()\n    }\n}\n\nimpl Focusable for ColorPickerState {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\n/// A color picker element.\n#[derive(IntoElement)]\npub struct ColorPicker {\n    id: ElementId,\n    style: StyleRefinement,\n    state: Entity<ColorPickerState>,\n    featured_colors: Option<Vec<Hsla>>,\n    label: Option<SharedString>,\n    icon: Option<Icon>,\n    size: Size,\n    anchor: Corner,\n}\n\nimpl ColorPicker {\n    /// Create a new color picker element with the given [`ColorPickerState`].\n    pub fn new(state: &Entity<ColorPickerState>) -> Self {\n        Self {\n            id: (\"color-picker\", state.entity_id()).into(),\n            style: StyleRefinement::default(),\n            state: state.clone(),\n            featured_colors: None,\n            size: Size::Medium,\n            label: None,\n            icon: None,\n            anchor: Corner::TopLeft,\n        }\n    }\n\n    /// Set the featured colors to be displayed in the color picker.\n    ///\n    /// This is used to display a set of colors that the user can quickly select from,\n    /// for example provided user's last used colors.\n    pub fn featured_colors(mut self, colors: Vec<Hsla>) -> Self {\n        self.featured_colors = Some(colors);\n        self\n    }\n\n    /// Set the icon to the color picker button.\n    ///\n    /// If this is set the color picker button will display the icon.\n    /// Else it will display the square color of the current value.\n    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {\n        self.icon = Some(icon.into());\n        self\n    }\n\n    /// Set the label to be displayed above the color picker.\n    ///\n    /// Default is `None`.\n    pub fn label(mut self, label: impl Into<SharedString>) -> Self {\n        self.label = Some(label.into());\n        self\n    }\n\n    /// Set the anchor corner of the color picker.\n    ///\n    /// Default is `Corner::TopLeft`.\n    pub fn anchor(mut self, anchor: Corner) -> Self {\n        self.anchor = anchor;\n        self\n    }\n\n    fn render_item(\n        &self,\n        color: Hsla,\n        clickable: bool,\n        window: &mut Window,\n        _: &mut App,\n    ) -> Stateful<Div> {\n        let state = self.state.clone();\n        div()\n            .id(SharedString::from(format!(\"color-{}\", color.to_hex())))\n            .h_5()\n            .w_5()\n            .bg(color)\n            .border_1()\n            .border_color(color.darken(0.1))\n            .when(clickable, |this| {\n                this.hover(|this| {\n                    this.border_color(color.darken(0.3))\n                        .bg(color.lighten(0.1))\n                        .shadow_xs()\n                })\n                .active(|this| this.border_color(color.darken(0.5)).bg(color.darken(0.2)))\n                .on_mouse_move(window.listener_for(&state, move |state, _, window, cx| {\n                    state.hovered_color = Some(color);\n                    state.state.update(cx, |input, cx| {\n                        input.set_value(color.to_hex(), window, cx);\n                    });\n                    cx.notify();\n                }))\n                .on_click(window.listener_for(\n                    &state,\n                    move |state, _, window, cx| {\n                        state.open = false;\n                        state.update_value(Some(color), true, window, cx);\n                        cx.notify();\n                    },\n                ))\n            })\n    }\n\n    fn render_colors(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        self.state.update(cx, |state, cx| {\n            if state.needs_slider_sync {\n                let value = state.value;\n                state.update_value(value, false, window, cx);\n            }\n        });\n\n        let active_tab = self.state.read(cx).active_tab;\n\n        let (slider_color, hovered_color) = {\n            let state = self.state.read(cx);\n            let slider_color = state\n                .hovered_color\n                .or(state.value)\n                .unwrap_or_else(|| hsla(0., 0., 0., 1.));\n            (slider_color, state.hovered_color)\n        };\n\n        v_flex()\n            .p_0p5()\n            .gap_3()\n            .child(\n                TabBar::new(\"mode\")\n                    .segmented()\n                    .selected_index(active_tab)\n                    .on_click(\n                        window.listener_for(&self.state, |state, ix: &usize, _, cx| {\n                            state.active_tab = *ix;\n                            cx.notify();\n                        }),\n                    )\n                    .child(Tab::new().flex_1().label(t!(\"ColorPicker.Palette\")))\n                    .child(Tab::new().flex_1().label(t!(\"ColorPicker.HSLA\"))),\n            )\n            .child(match active_tab {\n                0 => self.render_palette_panel(window, cx).into_any_element(),\n                _ => self\n                    .render_slider_tab_panel(slider_color, cx)\n                    .into_any_element(),\n            })\n            .when_some(hovered_color, |this, hovered_color| {\n                this.child(Divider::horizontal()).child(\n                    h_flex()\n                        .gap_2()\n                        .items_center()\n                        .child(\n                            div()\n                                .bg(hovered_color)\n                                .flex_shrink_0()\n                                .border_1()\n                                .border_color(hovered_color.darken(0.2))\n                                .size_5()\n                                .rounded(cx.theme().radius),\n                        )\n                        .child(Input::new(&self.state.read(cx).state).small()),\n                )\n            })\n    }\n\n    fn render_palette_panel(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let featured_colors = self.featured_colors.clone().unwrap_or(vec![\n            cx.theme().red,\n            cx.theme().red_light,\n            cx.theme().blue,\n            cx.theme().blue_light,\n            cx.theme().green,\n            cx.theme().green_light,\n            cx.theme().yellow,\n            cx.theme().yellow_light,\n            cx.theme().cyan,\n            cx.theme().cyan_light,\n            cx.theme().magenta,\n            cx.theme().magenta_light,\n        ]);\n\n        v_flex()\n            .gap_3()\n            .child(\n                h_flex().gap_1().children(\n                    featured_colors\n                        .iter()\n                        .map(|color| self.render_item(*color, true, window, cx)),\n                ),\n            )\n            .child(Divider::horizontal())\n            .child(\n                v_flex()\n                    .gap_1()\n                    .children(color_palettes().iter().map(|sub_colors| {\n                        h_flex().gap_1().children(\n                            sub_colors\n                                .iter()\n                                .rev()\n                                .map(|color| self.render_item(*color, true, window, cx)),\n                        )\n                    })),\n            )\n    }\n\n    fn render_slider_tab_panel(&self, slider_color: Hsla, cx: &mut App) -> impl IntoElement {\n        let hsla_sliders = self.state.read(cx).hsla_sliders.clone();\n        let steps = 96usize;\n        let hue_colors = (0..steps)\n            .map(|ix| {\n                let h = ix as f32 / (steps.saturating_sub(1)) as f32;\n                hsla(h, 1.0, 0.5, 1.0)\n            })\n            .collect::<Vec<_>>();\n        let saturation_start = hsla(slider_color.h, 0.0, slider_color.l, 1.0);\n        let saturation_end = hsla(slider_color.h, 1.0, slider_color.l, 1.0);\n        let lightness_colors = (0..steps)\n            .map(|ix| {\n                let l = ix as f32 / (steps.saturating_sub(1)) as f32;\n                hsla(slider_color.h, 1.0, l, 1.0)\n            })\n            .collect::<Vec<_>>();\n        let alpha_start = hsla(slider_color.h, slider_color.s, slider_color.l, 0.0);\n        let alpha_end = hsla(slider_color.h, slider_color.s, slider_color.l, 1.0);\n\n        let label_color = cx.theme().foreground.opacity(0.7);\n\n        v_flex()\n            .gap_2()\n            .child(\n                h_flex()\n                    .gap_2()\n                    .items_center()\n                    .child(\n                        div()\n                            .min_w_16()\n                            .text_xs()\n                            .text_color(label_color)\n                            .child(t!(\"ColorPicker.Hue\")),\n                    )\n                    .child(\n                        div()\n                            .relative()\n                            .flex()\n                            .items_center()\n                            .flex_1()\n                            .h_8()\n                            .child(self.render_slider_track(hue_colors, cx))\n                            .child(\n                                Slider::new(&hsla_sliders.hue)\n                                    .flex_1()\n                                    .bg(cx.theme().transparent),\n                            ),\n                    )\n                    .child(\n                        div()\n                            .w_10()\n                            .text_xs()\n                            .text_color(label_color)\n                            .text_align(TextAlign::Right)\n                            .child(format!(\"{:.0}\", slider_color.h * 360.)),\n                    ),\n            )\n            .child(\n                h_flex()\n                    .gap_2()\n                    .items_center()\n                    .child(\n                        div()\n                            .min_w_16()\n                            .text_xs()\n                            .text_color(label_color)\n                            .child(t!(\"ColorPicker.Saturation\")),\n                    )\n                    .child(\n                        div()\n                            .relative()\n                            .flex()\n                            .items_center()\n                            .flex_1()\n                            .h_8()\n                            .child(self.render_slider_track_gradient(\n                                saturation_start,\n                                saturation_end,\n                                cx,\n                            ))\n                            .child(\n                                Slider::new(&hsla_sliders.saturation)\n                                    .flex_1()\n                                    .bg(cx.theme().transparent),\n                            ),\n                    )\n                    .child(\n                        div()\n                            .w_10()\n                            .text_xs()\n                            .text_color(label_color)\n                            .text_align(TextAlign::Right)\n                            .child(format!(\"{:.0}\", slider_color.s * 100.)),\n                    ),\n            )\n            .child(\n                h_flex()\n                    .gap_2()\n                    .items_center()\n                    .child(\n                        div()\n                            .min_w_16()\n                            .text_xs()\n                            .text_color(label_color)\n                            .child(t!(\"ColorPicker.Lightness\")),\n                    )\n                    .child(\n                        div()\n                            .relative()\n                            .flex()\n                            .items_center()\n                            .flex_1()\n                            .h_8()\n                            .child(self.render_slider_track(lightness_colors, cx))\n                            .child(\n                                Slider::new(&hsla_sliders.lightness)\n                                    .flex_1()\n                                    .bg(cx.theme().transparent),\n                            ),\n                    )\n                    .child(\n                        div()\n                            .w_10()\n                            .text_xs()\n                            .text_color(label_color)\n                            .text_align(TextAlign::Right)\n                            .child(format!(\"{:.0}\", slider_color.l * 100.)),\n                    ),\n            )\n            .child(\n                h_flex()\n                    .gap_2()\n                    .items_center()\n                    .child(\n                        div()\n                            .min_w_16()\n                            .text_xs()\n                            .text_color(label_color)\n                            .child(t!(\"ColorPicker.Alpha\")),\n                    )\n                    .child(\n                        div()\n                            .relative()\n                            .flex()\n                            .items_center()\n                            .flex_1()\n                            .h_8()\n                            .child(self.render_slider_track_gradient(alpha_start, alpha_end, cx))\n                            .child(\n                                Slider::new(&hsla_sliders.alpha)\n                                    .flex_1()\n                                    .bg(cx.theme().transparent),\n                            ),\n                    )\n                    .child(\n                        div()\n                            .w_10()\n                            .text_xs()\n                            .text_color(label_color)\n                            .text_align(TextAlign::Right)\n                            .child(format!(\"{:.0}\", slider_color.a * 100.)),\n                    ),\n            )\n    }\n\n    fn render_slider_track(&self, colors: Vec<Hsla>, _: &App) -> impl IntoElement {\n        h_flex()\n            .absolute()\n            .left_0()\n            .right_0()\n            .h_2_5()\n            .overflow_hidden()\n            .children(\n                colors\n                    .into_iter()\n                    .map(|color| div().flex_1().h_full().bg(color)),\n            )\n    }\n\n    fn render_slider_track_gradient(&self, start: Hsla, end: Hsla, _: &App) -> impl IntoElement {\n        div()\n            .absolute()\n            .left_0()\n            .right_0()\n            .h_2_5()\n            .overflow_hidden()\n            .bg(linear_gradient(\n                90.,\n                linear_color_stop(start, 0.),\n                linear_color_stop(end, 1.),\n            ))\n    }\n}\n\nimpl Sizable for ColorPicker {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Focusable for ColorPicker {\n    fn focus_handle(&self, cx: &App) -> FocusHandle {\n        self.state.read(cx).focus_handle.clone()\n    }\n}\n\nimpl Styled for ColorPicker {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for ColorPicker {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let state = self.state.read(cx);\n        let display_title: SharedString = if let Some(value) = state.value {\n            value.to_hex()\n        } else {\n            \"\".to_string()\n        }\n        .into();\n\n        let focus_handle = state.focus_handle.clone().tab_stop(true);\n\n        div()\n            .id(self.id.clone())\n            .key_context(CONTEXT)\n            .track_focus(&focus_handle)\n            .on_action(window.listener_for(&self.state, ColorPickerState::on_confirm))\n            .child(\n                Popover::new(\"popover\")\n                    .open(state.open)\n                    .w_72()\n                    .on_open_change(\n                        window.listener_for(&self.state, |this, open: &bool, _, cx| {\n                            this.open = *open;\n                            cx.notify();\n                        }),\n                    )\n                    .trigger(ColorPickerButton {\n                        id: \"trigger\".into(),\n                        size: self.size,\n                        label: self.label.clone(),\n                        value: state.value,\n                        tooltip: if display_title.is_empty() {\n                            None\n                        } else {\n                            Some(display_title.clone())\n                        },\n                        icon: self.icon.clone(),\n                        selected: false,\n                    })\n                    .child(self.render_colors(window, cx)),\n            )\n    }\n}\n\n#[derive(IntoElement)]\nstruct ColorPickerButton {\n    id: ElementId,\n    selected: bool,\n    icon: Option<Icon>,\n    value: Option<Hsla>,\n    size: Size,\n    label: Option<SharedString>,\n    tooltip: Option<SharedString>,\n}\n\nimpl Selectable for ColorPickerButton {\n    fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        self.selected\n    }\n}\n\nimpl Sizable for ColorPickerButton {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl RenderOnce for ColorPickerButton {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let has_icon = self.icon.is_some();\n        h_flex()\n            .id(self.id)\n            .gap_2()\n            .children(self.icon)\n            .when(!has_icon, |this| {\n                this.child(\n                    div()\n                        .id(\"square\")\n                        .bg(cx.theme().background)\n                        .border_1()\n                        .border_color(cx.theme().input)\n                        .when(cx.theme().shadow, |this| this.shadow_xs())\n                        .rounded(cx.theme().radius)\n                        .overflow_hidden()\n                        .size_with(self.size)\n                        .when_some(self.value, |this, value| {\n                            this.bg(value)\n                                .border_color(value.darken(0.3))\n                                .when(self.selected, |this| this.border_2())\n                        })\n                        .when_some(self.tooltip, |this, tooltip| {\n                            this.tooltip(move |_, cx| {\n                                cx.new(|_| Tooltip::new(tooltip.clone())).into()\n                            })\n                        }),\n                )\n            })\n            .when_some(self.label, |this, label| this.child(label))\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/description_list.rs",
    "content": "use gpui::{\n    div, prelude::FluentBuilder as _, px, AnyElement, App, Axis, DefiniteLength, IntoElement,\n    ParentElement, RenderOnce, SharedString, Styled, Window,\n};\n\nuse crate::{h_flex, text::Text, v_flex, ActiveTheme as _, AxisExt, Sizable, Size};\n\n/// A description list.\n#[derive(IntoElement)]\npub struct DescriptionList {\n    items: Vec<DescriptionItem>,\n    size: Size,\n    layout: Axis,\n    label_width: DefiniteLength,\n    bordered: bool,\n    columns: usize,\n}\n\n/// Item for the [`DescriptionList`].\npub enum DescriptionItem {\n    Item {\n        label: DescriptionText,\n        value: DescriptionText,\n        span: usize,\n    },\n    Divider,\n}\n\n/// Text for the label or value in the [`DescriptionList`].\n#[derive(IntoElement)]\npub enum DescriptionText {\n    String(SharedString),\n    Text(Text),\n    AnyElement(AnyElement),\n}\n\nimpl From<&str> for DescriptionText {\n    fn from(text: &str) -> Self {\n        DescriptionText::String(SharedString::from(text.to_string()))\n    }\n}\n\nimpl From<Text> for DescriptionText {\n    fn from(text: Text) -> Self {\n        DescriptionText::Text(text)\n    }\n}\n\nimpl From<AnyElement> for DescriptionText {\n    fn from(element: AnyElement) -> Self {\n        DescriptionText::AnyElement(element)\n    }\n}\n\nimpl From<SharedString> for DescriptionText {\n    fn from(text: SharedString) -> Self {\n        DescriptionText::String(text)\n    }\n}\n\nimpl From<String> for DescriptionText {\n    fn from(text: String) -> Self {\n        DescriptionText::String(SharedString::from(text))\n    }\n}\n\nimpl RenderOnce for DescriptionText {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        match self {\n            DescriptionText::String(text) => div().child(text).into_any_element(),\n            DescriptionText::Text(text) => text.into_any_element(),\n            DescriptionText::AnyElement(element) => element,\n        }\n    }\n}\n\nimpl DescriptionItem {\n    /// Create a new description item, with a label.\n    ///\n    /// The value is an empty element.\n    pub fn new(label: impl Into<DescriptionText>) -> Self {\n        DescriptionItem::Item {\n            label: label.into(),\n            value: \"\".into(),\n            span: 1,\n        }\n    }\n\n    /// Set the element value of the item.\n    pub fn value(mut self, value: impl Into<DescriptionText>) -> Self {\n        let new_value = value.into();\n        if let DescriptionItem::Item { value, .. } = &mut self {\n            *value = new_value;\n        }\n        self\n    }\n\n    /// Set the span of the item.\n    ///\n    /// This method only works for [`DescriptionItem::Item`].\n    pub fn span(mut self, span: usize) -> Self {\n        let val = span;\n        if let DescriptionItem::Item { span, .. } = &mut self {\n            *span = val;\n        }\n        self\n    }\n\n    fn _label(&self) -> Option<&DescriptionText> {\n        match self {\n            DescriptionItem::Item { label, .. } => Some(label),\n            _ => None,\n        }\n    }\n\n    fn _span(&self) -> Option<usize> {\n        match self {\n            DescriptionItem::Item { span, .. } => Some(*span),\n            _ => None,\n        }\n    }\n}\n\nimpl DescriptionList {\n    /// Create a new description list with the default layout (Horizontal).\n    pub fn new() -> Self {\n        Self {\n            items: Vec::new(),\n            layout: Axis::Horizontal,\n            label_width: px(120.).into(),\n            size: Size::default(),\n            bordered: true,\n            columns: 3,\n        }\n    }\n\n    /// Create a vertical description list.\n    pub fn vertical() -> Self {\n        Self::new().layout(Axis::Vertical)\n    }\n\n    /// Create a horizontal description list, the default.\n    pub fn horizontal() -> Self {\n        Self::new().layout(Axis::Horizontal)\n    }\n\n    /// Set the width of the label, only works for horizontal layout.\n    ///\n    /// Default is `120px`.\n    pub fn label_width(mut self, label_width: impl Into<DefiniteLength>) -> Self {\n        self.label_width = label_width.into();\n        self\n    }\n\n    /// Set the layout of the description list.\n    pub fn layout(mut self, layout: Axis) -> Self {\n        self.layout = layout;\n        self\n    }\n\n    /// Set the border of the description list, default is `true`.\n    ///\n    /// `Horizontal` layout only.\n    pub fn bordered(mut self, bordered: bool) -> Self {\n        self.bordered = bordered;\n        self\n    }\n\n    /// Set the number of columns in the description list, default is `3`.\n    ///\n    /// A value between `1` and `10` is allowed.\n    pub fn columns(mut self, columns: usize) -> Self {\n        self.columns = columns.clamp(1, 10);\n        self\n    }\n\n    /// Add a [`DescriptionItem::Item`] to the list.\n    pub fn item(\n        mut self,\n        label: impl Into<DescriptionText>,\n        value: impl Into<DescriptionText>,\n        span: usize,\n    ) -> Self {\n        self.items.push(DescriptionItem::Item {\n            label: label.into(),\n            value: value.into(),\n            span,\n        });\n        self\n    }\n\n    /// Add a child to the list.\n    pub fn child(mut self, child: impl Into<DescriptionItem>) -> Self {\n        self.items.push(child.into());\n        self\n    }\n\n    /// Add children to the list.\n    pub fn children(\n        mut self,\n        children: impl IntoIterator<Item = impl Into<DescriptionItem>>,\n    ) -> Self {\n        self.items\n            .extend(children.into_iter().map(Into::into).collect::<Vec<_>>());\n        self\n    }\n\n    /// Add a divider to the list.\n    pub fn divider(mut self) -> Self {\n        self.items.push(DescriptionItem::Divider);\n        self\n    }\n\n    fn group_item_rows(items: Vec<DescriptionItem>, columns: usize) -> Vec<Vec<DescriptionItem>> {\n        let mut rows = vec![];\n        let mut current_span = 0;\n        for item in items.into_iter() {\n            let span = item._span().unwrap_or(columns);\n            if rows.is_empty() {\n                rows.push(vec![]);\n            }\n            if current_span + span > columns {\n                rows.push(vec![]);\n                current_span = 0;\n            }\n            let last_group = rows.last_mut().unwrap();\n            last_group.push(item);\n            current_span += span;\n        }\n        // Remove last empty rows if it exists\n        while let Some(last_group) = rows.last() {\n            if !last_group.is_empty() {\n                break;\n            }\n\n            rows.pop();\n        }\n\n        rows\n    }\n}\n\nimpl Sizable for DescriptionList {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl RenderOnce for DescriptionList {\n    fn render(self, _: &mut Window, cx: &mut gpui::App) -> impl gpui::IntoElement {\n        let base_gap = match self.size {\n            Size::XSmall | Size::Small => px(2.),\n            Size::Medium => px(4.),\n            Size::Large => px(8.),\n            _ => px(4.),\n        };\n\n        // Only for Horizontal layout\n        let (mut padding_x, mut padding_y) = match self.size {\n            Size::XSmall | Size::Small => (px(4.), px(2.)),\n            Size::Medium => (px(8.), px(4.)),\n            Size::Large => (px(12.), px(6.)),\n            _ => (px(8.), px(4.)),\n        };\n\n        let label_width = if self.layout.is_horizontal() {\n            Some(self.label_width)\n        } else {\n            None\n        };\n        if !self.bordered {\n            padding_x = px(0.);\n            padding_y = px(0.);\n        }\n        let gap = if self.bordered { px(0.) } else { base_gap };\n\n        // Group items by columns\n        let rows = Self::group_item_rows(self.items, self.columns);\n        let rows_len = rows.len();\n\n        v_flex()\n            .gap(gap)\n            .overflow_hidden()\n            .when(self.bordered, |this| {\n                this.rounded(padding_x)\n                    .border_1()\n                    .border_color(cx.theme().border)\n            })\n            .children(rows.into_iter().enumerate().map(|(ix, items)| {\n                let is_last = ix == rows_len - 1;\n                h_flex()\n                    .when(self.bordered && !is_last, |this| {\n                        this.border_b_1().border_color(cx.theme().border)\n                    })\n                    .children({\n                        items.into_iter().enumerate().map(|(item_ix, item)| {\n                            let is_first_col = item_ix == 0;\n\n                            match item {\n                                DescriptionItem::Item { label, value, .. } => {\n                                    let el = if self.layout.is_vertical() {\n                                        v_flex()\n                                    } else {\n                                        div().flex().flex_row().h_full()\n                                    };\n\n                                    el.flex_1()\n                                        .overflow_x_hidden()\n                                        .child(\n                                            div()\n                                                .when(self.layout.is_horizontal(), |this| {\n                                                    this.h_full()\n                                                })\n                                                .text_color(\n                                                    cx.theme().description_list_label_foreground,\n                                                )\n                                                .text_sm()\n                                                .px(padding_x)\n                                                .py(padding_y)\n                                                .when(self.bordered, |this| {\n                                                    this.when(self.layout.is_horizontal(), |this| {\n                                                        this.border_r_1()\n                                                            .when(!is_first_col, |this| {\n                                                                this.border_l_1()\n                                                            })\n                                                    })\n                                                    .when(self.layout.is_vertical(), |this| {\n                                                        this.border_b_1()\n                                                    })\n                                                    .border_color(cx.theme().border)\n                                                    .bg(cx.theme().description_list_label)\n                                                })\n                                                .map(|this| match label_width {\n                                                    Some(label_width) => {\n                                                        this.w(label_width).flex_shrink_0()\n                                                    }\n                                                    None => this,\n                                                })\n                                                .child(label),\n                                        )\n                                        .child(\n                                            div()\n                                                .flex_1()\n                                                .px(padding_x)\n                                                .py(padding_y)\n                                                .overflow_hidden()\n                                                .child(value),\n                                        )\n                                }\n                                _ => div().h_2().w_full().when(self.bordered, |this| {\n                                    this.bg(cx.theme().description_list_label)\n                                }),\n                            }\n                        })\n                    })\n            }))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::DescriptionItem;\n\n    #[test]\n    fn test_group_item_rows() {\n        let items = vec![\n            DescriptionItem::new(\"test1\"),\n            DescriptionItem::new(\"test2\").span(2),\n            DescriptionItem::new(\"test3\"),\n            DescriptionItem::new(\"test4\"),\n            DescriptionItem::new(\"test5\"),\n            DescriptionItem::new(\"test6\").span(3),\n            DescriptionItem::new(\"test7\"),\n        ];\n        let rows = super::DescriptionList::group_item_rows(items, 3);\n        assert_eq!(rows.len(), 4);\n        assert_eq!(rows[0].len(), 2);\n        assert_eq!(rows[1].len(), 3);\n        assert_eq!(rows[2].len(), 1);\n        assert_eq!(rows[3].len(), 1);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dialog/alert_dialog.rs",
    "content": "use gpui::{\n    AnyElement, App, ClickEvent, InteractiveElement as _, IntoElement, MouseButton, ParentElement,\n    Pixels, RenderOnce, StyleRefinement, Styled, Window, div, prelude::FluentBuilder as _,\n};\n\nuse crate::{\n    StyledExt as _, WindowExt as _,\n    dialog::{\n        Dialog, DialogButtonProps, DialogDescription, DialogFooter, DialogHeader, DialogTitle,\n    },\n};\n\n/// AlertDialog is a modal dialog that interrupts the user with important content\n/// and expects a response.\n///\n/// It is built on top of the Dialog component with opinionated defaults:\n/// - Footer buttons are center-aligned (vs right-aligned in Dialog)\n/// - Icon is optional (disabled by default, enable with `.show_icon(true)`)\n/// - Simplified API for common alert scenarios\n/// - Uses declarative DialogHeader, DialogTitle, DialogDescription, and DialogFooter components\n/// - Supports both imperative and declarative API styles\n///\n/// # Examples\n///\n/// ## Imperative API (using WindowExt)\n///\n/// ```ignore\n/// use gpui_component::{AlertDialog, alert::AlertVariant};\n///\n/// // Using WindowExt trait\n/// window.open_alert_dialog(cx, |alert, _, _| {\n///     alert\n///         .title(\"Unsaved Changes\")\n///         .description(\"You have unsaved changes. Are you sure you want to leave?\")\n///         .show_cancel(true)\n/// });\n/// ```\n///\n/// ## Declarative API (using trigger and content)\n///\n/// ```ignore\n/// use gpui_component::{AlertDialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter};\n///\n/// AlertDialog::new(cx)\n///     .trigger(Button::new(\"delete\").label(\"Delete\"))\n///     .content(|content, _, cx| {\n///         content\n///             .child(\n///                 DialogHeader::new()\n///                     .items_center()\n///                     .child(DialogTitle::new().child(\"Delete File\"))\n///                     .child(DialogDescription::new().child(\"Are you sure?\"))\n///             )\n///             .child(\n///                 DialogFooter::new()\n///                     .justify_center()\n///                     .child(Button::new(\"cancel\").label(\"Cancel\"))\n///                     .child(Button::new(\"confirm\").label(\"Delete\"))\n///             )\n///     })\n/// ```\n#[derive(IntoElement)]\npub struct AlertDialog {\n    base: Dialog,\n    trigger: Option<AnyElement>,\n    icon: Option<AnyElement>,\n    title: Option<AnyElement>,\n    description: Option<AnyElement>,\n    button_props: DialogButtonProps,\n    children: Vec<AnyElement>,\n}\n\nimpl AlertDialog {\n    /// Create a new AlertDialog.\n    ///\n    /// By default, the dialog is not overlay closable with a OK button.\n    ///\n    /// You can change this with `.overlay_closable(true)`.\n    pub fn new(cx: &mut App) -> Self {\n        Self {\n            base: Dialog::new(cx).overlay_closable(false).close_button(false),\n            trigger: None,\n            icon: None,\n            title: None,\n            description: None,\n            button_props: DialogButtonProps::default(),\n            children: Vec::new(),\n        }\n    }\n\n    /// Set to use confirm dialog, with OK and Cancel buttons.\n    ///\n    /// The default of [`AlertDialog`] has OK button.\n    pub fn confirm(mut self) -> Self {\n        self.button_props.show_cancel = true;\n        self\n    }\n\n    /// Sets the trigger element for the alert dialog.\n    ///\n    /// When a trigger is set, the dialog will render as a trigger element that opens the dialog when clicked.\n    ///\n    /// **Note**: When using `.trigger()`, you should also use `.content()` to define the dialog content\n    /// declaratively instead of using `.title()`, `.description()`, etc.\n    ///\n    /// The `title`, `description`, `icon`, and `button_props` will be ignored when used together with `.trigger()`.\n    pub fn trigger(mut self, trigger: impl IntoElement) -> Self {\n        self.trigger = Some(trigger.into_any_element());\n        self\n    }\n\n    /// Sets the content builder for declarative API.\n    ///\n    /// When using this method, you define the dialog content using declarative components like\n    /// `DialogHeader`, `DialogTitle`, `DialogDescription`, and `DialogFooter`.\n    ///\n    /// This method is typically used together with `.trigger()` for a fully declarative API.\n    ///\n    /// # Examples\n    ///\n    /// ```ignore\n    /// AlertDialog::new(cx)\n    ///     .trigger(Button::new(\"delete\").label(\"Delete\"))\n    ///     .content(|content, _, cx| {\n    ///         content\n    ///             .child(DialogHeader::new().child(DialogTitle::new().child(\"Confirm\")))\n    ///             .child(DialogFooter::new().child(Button::new(\"ok\").label(\"OK\")))\n    ///     })\n    /// ```\n    pub fn content<F>(mut self, builder: F) -> Self\n    where\n        F: Fn(crate::dialog::DialogContent, &mut Window, &mut App) -> crate::dialog::DialogContent\n            + 'static,\n    {\n        self.base = self.base.content(builder);\n        self\n    }\n\n    /// Sets the footer builder for declarative API.\n    ///\n    /// This is used to define the footer content using declarative components like `DialogFooter`.\n    ///\n    /// If not set, a default footer with OK and optional Cancel button will be used.\n    pub fn footer(mut self, footer: impl IntoElement) -> Self {\n        self.base = self.base.footer(footer);\n        self\n    }\n\n    #[track_caller]\n    fn debug_assert_no_trigger(&self) {\n        debug_assert!(\n            self.trigger.is_none() && self.base.content_builder.is_none(),\n            \"Cannot set this property when trigger is used. Use content() to define dialog content instead.\"\n        );\n    }\n\n    /// Sets the icon of the alert dialog, default is None.\n    #[track_caller]\n    pub fn icon(mut self, icon: impl IntoElement) -> Self {\n        self.debug_assert_no_trigger();\n        self.icon = Some(icon.into_any_element());\n        self\n    }\n\n    /// Sets the title of the alert dialog.\n    #[track_caller]\n    pub fn title(mut self, title: impl IntoElement) -> Self {\n        self.debug_assert_no_trigger();\n        self.title = Some(title.into_any_element());\n        self\n    }\n\n    /// Sets the description of the alert dialog.\n    #[track_caller]\n    pub fn description(mut self, description: impl IntoElement) -> Self {\n        self.debug_assert_no_trigger();\n        self.description = Some(description.into_any_element());\n        self\n    }\n\n    /// Set the button props of the alert dialog.\n    ///\n    /// Use this to configure button text, variants, and visibility.\n    ///\n    /// # Examples\n    ///\n    /// ```ignore\n    /// alert.button_props(\n    ///     DialogButtonProps::default()\n    ///         .ok_text(\"Delete\")\n    ///         .ok_variant(ButtonVariant::Danger)\n    ///         .cancel_text(\"Keep\")\n    ///         .show_cancel(true)\n    /// )\n    /// ```\n    #[track_caller]\n    pub fn button_props(mut self, button_props: DialogButtonProps) -> Self {\n        self.debug_assert_no_trigger();\n        self.button_props = button_props;\n        self\n    }\n\n    /// Sets the width of the alert dialog, defaults to 420px.\n    pub fn width(mut self, width: impl Into<Pixels>) -> Self {\n        self.base = self.base.width(width);\n        self\n    }\n\n    /// Show cancel button. Default is false.\n    pub fn show_cancel(mut self, show_cancel: bool) -> Self {\n        self.button_props = self.button_props.show_cancel(show_cancel);\n        self\n    }\n\n    /// Set the overlay closable of the alert dialog, defaults to `false`.\n    ///\n    /// When the overlay is clicked, the dialog will be closed.\n    pub fn overlay_closable(mut self, overlay_closable: bool) -> Self {\n        self.base = self.base.overlay_closable(overlay_closable);\n        self\n    }\n\n    /// Set the close button of the alert dialog, defaults to `false`.\n    pub fn close_button(mut self, close_button: bool) -> Self {\n        self.base = self.base.close_button(close_button);\n        self\n    }\n\n    /// Set whether to support keyboard esc to close the dialog, defaults to `true`.\n    pub fn keyboard(mut self, keyboard: bool) -> Self {\n        self.base = self.base.keyboard(keyboard);\n        self\n    }\n\n    /// Sets the callback for when the alert dialog is closed.\n    ///\n    /// Called after [`Self::on_action`] or [`Self::on_cancel`] callback.\n    pub fn on_close(\n        mut self,\n        on_close: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.base = self.base.on_close(on_close);\n        self\n    }\n\n    /// Sets the callback for when the OK/action button is clicked.\n    ///\n    /// The callback should return `true` to close the dialog, if return `false` the dialog will not be closed.\n    pub fn on_ok(\n        mut self,\n        on_ok: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,\n    ) -> Self {\n        self.button_props = self.button_props.on_ok(on_ok);\n        self\n    }\n\n    /// Sets the callback for when the alert dialog has been canceled.\n    ///\n    /// The callback should return `true` to close the dialog, if return `false` the dialog will not be closed.\n    pub fn on_cancel(\n        mut self,\n        on_cancel: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,\n    ) -> Self {\n        self.button_props = self.button_props.on_cancel(on_cancel);\n        self\n    }\n\n    /// Convert AlertDialog into a configured Dialog.\n    pub(crate) fn into_dialog(self, window: &mut Window, cx: &mut App) -> Dialog {\n        let button_props = self.button_props.clone();\n        let has_footer = self.base.footer.is_some();\n        let has_header = self.icon.is_some() || self.title.is_some() || self.description.is_some();\n\n        self.base\n            .button_props(button_props.clone())\n            .when(has_header, |this| {\n                this.header(\n                    DialogHeader::new()\n                        .when_some(self.icon, |this, icon| this.child(icon))\n                        .when_some(self.title, |this, title| {\n                            this.child(DialogTitle::new().child(title))\n                        })\n                        .when_some(self.description, |this, desc| {\n                            this.child(DialogDescription::new().child(desc))\n                        }),\n                )\n            })\n            .children(self.children)\n            .when(!has_footer, |this| {\n                // Default footer for AlertDialog if user doesn't provide one, with OK and optional Cancel button\n                this.footer(\n                    DialogFooter::new()\n                        .when(button_props.show_cancel, |this| {\n                            this.child(button_props.render_cancel(window, cx))\n                        })\n                        .child(button_props.render_ok(window, cx)),\n                )\n            })\n    }\n}\n\nimpl Styled for AlertDialog {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.base.style\n    }\n}\n\nimpl ParentElement for AlertDialog {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl AlertDialog {\n    fn render_trigger(self, trigger: AnyElement, _: &mut Window, _: &mut App) -> AnyElement {\n        let content_builder = self.base.content_builder.clone();\n        let style = self.base.style.clone();\n        let props = self.base.props.clone();\n        let button_props = self.button_props.clone();\n\n        div()\n            .on_mouse_down(MouseButton::Left, move |_, window, cx| {\n                let content_builder = content_builder.clone();\n                let style = style.clone();\n                let props = props.clone();\n                let button_props = button_props.clone();\n                window.open_dialog(cx, move |dialog, _, _| {\n                    dialog\n                        .refine_style(&style)\n                        .button_props(button_props.clone())\n                        .with_props(props.clone())\n                        .when_some(content_builder.clone(), |this, content_builder| {\n                            this.content(move |content, window, cx| {\n                                content_builder(content, window, cx)\n                            })\n                        })\n                });\n                cx.stop_propagation();\n            })\n            .child(trigger)\n            .into_any_element()\n    }\n}\n\nimpl RenderOnce for AlertDialog {\n    fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        if let Some(trigger) = self.trigger.take() {\n            // If a trigger is provided, render the trigger element that opens the dialog\n            self.render_trigger(trigger, window, cx)\n        } else {\n            // Otherwise, render the dialog content directly\n            self.into_dialog(window, cx).into_any_element()\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dialog/content.rs",
    "content": "use gpui::{\n    AnyElement, App, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, Window,\n};\n\nuse crate::{ActiveTheme as _, StyledExt as _, v_flex};\n\n/// Content container for a dialog.\n#[derive(IntoElement)]\npub struct DialogContent {\n    style: StyleRefinement,\n    children: Vec<AnyElement>,\n}\n\nimpl DialogContent {\n    pub fn new() -> Self {\n        Self { style: StyleRefinement::default(), children: Vec::new() }\n    }\n}\n\nimpl ParentElement for DialogContent {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Styled for DialogContent {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for DialogContent {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        v_flex()\n            .w_full()\n            .flex_1()\n            .rounded(cx.theme().radius_lg)\n            .refine_style(&self.style)\n            .children(self.children)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dialog/description.rs",
    "content": "use gpui::{\n    AnyElement, App, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,\n    StyleRefinement, Styled, Window, div,\n};\n\nuse crate::{ActiveTheme as _, StyledExt as _};\n\n/// Description element for a dialog header.\n///\n/// Typically used inside a DialogHeader component to provide additional context.\n///\n/// # Examples\n///\n/// ```ignore\n/// DialogDescription::new(\"This action cannot be undone.\")\n/// ```\n#[derive(IntoElement)]\npub struct DialogDescription {\n    style: StyleRefinement,\n    children: Vec<AnyElement>,\n}\n\nimpl DialogDescription {\n    pub fn new() -> Self {\n        Self { style: StyleRefinement::default(), children: vec![] }\n    }\n}\n\nimpl ParentElement for DialogDescription {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Styled for DialogDescription {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for DialogDescription {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        div()\n            .id(\"dialog-description\")\n            .text_sm()\n            .text_color(cx.theme().muted_foreground)\n            .refine_style(&self.style)\n            .children(self.children)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dialog/dialog.rs",
    "content": "use std::{rc::Rc, sync::LazyLock, time::Duration};\n\nuse gpui::{\n    Animation, AnimationExt as _, AnyElement, App, Bounds, BoxShadow, ClickEvent, Edges,\n    FocusHandle, Hsla, InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement,\n    Pixels, Point, RenderOnce, SharedString, StyleRefinement, Styled, Window, WindowControlArea,\n    actions, anchored, div, hsla, point, prelude::FluentBuilder, px,\n};\nuse rust_i18n::t;\n\nuse crate::{\n    ActiveTheme as _, FocusTrapElement as _, IconName, Root, Sizable as _, StyledExt,\n    TITLE_BAR_HEIGHT, WindowExt as _,\n    animation::cubic_bezier,\n    button::{Button, ButtonVariant, ButtonVariants as _},\n    dialog::{DialogContent, DialogTitle},\n    scroll::ScrollableElement as _,\n    v_flex,\n};\n\npub static ANIMATION_DURATION: LazyLock<Duration> = LazyLock::new(|| Duration::from_secs_f64(0.25));\nconst CONTEXT: &str = \"Dialog\";\n\nactions!(dialog, [CancelDialog, ConfirmDialog]);\n\npub(crate) fn init(cx: &mut App) {\n    cx.bind_keys([\n        KeyBinding::new(\"escape\", CancelDialog, Some(CONTEXT)),\n        KeyBinding::new(\"enter\", ConfirmDialog, Some(CONTEXT)),\n    ]);\n}\n\n/// Dialog button props.\n#[derive(Clone)]\npub struct DialogButtonProps {\n    pub(crate) ok_text: Option<SharedString>,\n    pub(crate) ok_variant: ButtonVariant,\n    pub(crate) cancel_text: Option<SharedString>,\n    pub(crate) cancel_variant: ButtonVariant,\n    pub(crate) show_cancel: bool,\n    pub(crate) on_ok: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static>,\n    pub(crate) on_cancel: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static>,\n    pub(crate) on_close: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,\n}\n\nimpl Default for DialogButtonProps {\n    fn default() -> Self {\n        Self {\n            ok_text: None,\n            ok_variant: ButtonVariant::Primary,\n            cancel_text: None,\n            cancel_variant: ButtonVariant::default(),\n            show_cancel: false,\n            on_ok: Rc::new(|_, _, _| true),\n            on_cancel: Rc::new(|_, _, _| true),\n            on_close: Rc::new(|_, _, _| {}),\n        }\n    }\n}\n\nimpl DialogButtonProps {\n    /// Sets the text of the OK button. Default is `OK`.\n    pub fn ok_text(mut self, ok_text: impl Into<SharedString>) -> Self {\n        self.ok_text = Some(ok_text.into());\n        self\n    }\n\n    /// Sets the variant of the OK button. Default is `ButtonVariant::Primary`.\n    pub fn ok_variant(mut self, ok_variant: ButtonVariant) -> Self {\n        self.ok_variant = ok_variant;\n        self\n    }\n\n    /// Sets the text of the Cancel button. Default is `Cancel`.\n    pub fn cancel_text(mut self, cancel_text: impl Into<SharedString>) -> Self {\n        self.cancel_text = Some(cancel_text.into());\n        self\n    }\n\n    /// Sets the variant of the Cancel button. Default is `ButtonVariant::default()`.\n    pub fn cancel_variant(mut self, cancel_variant: ButtonVariant) -> Self {\n        self.cancel_variant = cancel_variant;\n        self\n    }\n\n    /// Sets whether to show the Cancel button. Default is `false`.\n    pub fn show_cancel(mut self, show_cancel: bool) -> Self {\n        self.show_cancel = show_cancel;\n        self\n    }\n\n    /// Sets the callback for when the dialog is has been confirmed.\n    ///\n    /// The callback should return `true` to close the dialog, if return `false` the dialog will not be closed.\n    pub fn on_ok(\n        mut self,\n        on_ok: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,\n    ) -> Self {\n        self.on_ok = Rc::new(on_ok);\n        self\n    }\n\n    /// Sets the callback for when the dialog is has been canceled.\n    ///\n    /// The callback should return `true` to close the dialog, if return `false` the dialog will not be closed.\n    pub fn on_cancel(\n        mut self,\n        on_cancel: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,\n    ) -> Self {\n        self.on_cancel = Rc::new(on_cancel);\n        self\n    }\n\n    pub(crate) fn render_ok(&self, _: &mut Window, _: &mut App) -> AnyElement {\n        let on_ok = self.on_ok.clone();\n        let on_close = self.on_close.clone();\n\n        let ok_text = self\n            .ok_text\n            .clone()\n            .unwrap_or_else(|| t!(\"Dialog.ok\").into());\n        let ok_variant = self.ok_variant;\n\n        Button::new(\"ok\")\n            .label(ok_text)\n            .with_variant(ok_variant)\n            .on_click({\n                let on_ok = on_ok.clone();\n                let on_close = on_close.clone();\n\n                move |_, window, cx| {\n                    if on_ok(&ClickEvent::default(), window, cx) {\n                        window.close_dialog(cx);\n                        on_close(&ClickEvent::default(), window, cx);\n                    }\n                }\n            })\n            .into_any_element()\n    }\n\n    pub(crate) fn render_cancel(&self, _: &mut Window, _: &mut App) -> AnyElement {\n        let on_cancel = self.on_cancel.clone();\n        let on_close = self.on_close.clone();\n        let cancel_text = self\n            .cancel_text\n            .clone()\n            .unwrap_or_else(|| t!(\"Dialog.cancel\").into());\n        let cancel_variant = self.cancel_variant;\n\n        Button::new(\"cancel\")\n            .label(cancel_text)\n            .with_variant(cancel_variant)\n            .on_click({\n                let on_cancel = on_cancel.clone();\n                let on_close = on_close.clone();\n                move |_, window, cx| {\n                    if !on_cancel(&ClickEvent::default(), window, cx) {\n                        return;\n                    }\n\n                    window.close_dialog(cx);\n                    on_close(&ClickEvent::default(), window, cx);\n                }\n            })\n            .into_any_element()\n    }\n}\n\ntype ContentBuilderFn = Rc<dyn Fn(DialogContent, &mut Window, &mut App) -> DialogContent + 'static>;\n\n#[derive(Clone)]\npub(crate) struct DialogProps {\n    width: Pixels,\n    max_width: Option<Pixels>,\n    margin_top: Option<Pixels>,\n    close_button: bool,\n\n    overlay: bool,\n    overlay_closable: bool,\n    pub(crate) overlay_visible: bool,\n    keyboard: bool,\n}\n\nimpl Default for DialogProps {\n    fn default() -> Self {\n        Self {\n            margin_top: None,\n            width: px(448.),\n            max_width: None,\n            overlay: true,\n            keyboard: true,\n            overlay_visible: false,\n            close_button: true,\n            overlay_closable: true,\n        }\n    }\n}\n\n/// A modal to display content in a dialog box.\n#[derive(IntoElement)]\npub struct Dialog {\n    pub(crate) style: StyleRefinement,\n    children: Vec<AnyElement>,\n    trigger: Option<AnyElement>,\n    title: Option<AnyElement>,\n    pub(crate) header: Option<AnyElement>,\n    pub(crate) footer: Option<AnyElement>,\n    pub(crate) content_builder: Option<ContentBuilderFn>,\n    pub(crate) props: DialogProps,\n\n    button_props: DialogButtonProps,\n\n    /// This will be change when open the dialog, the focus handle is create when open the dialog.\n    pub(crate) focus_handle: FocusHandle,\n    pub(crate) layer_ix: usize,\n}\n\npub(crate) fn overlay_color(overlay: bool, cx: &App) -> Hsla {\n    if !overlay {\n        return hsla(0., 0., 0., 0.);\n    }\n\n    cx.theme().overlay\n}\n\nimpl Dialog {\n    /// Create a new dialog.\n    pub fn new(cx: &mut App) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            style: StyleRefinement::default(),\n            trigger: None,\n            title: None,\n            header: None,\n            footer: None,\n            content_builder: None,\n            props: DialogProps::default(),\n            children: Vec::new(),\n            layer_ix: 0,\n            button_props: DialogButtonProps::default(),\n        }\n    }\n\n    /// Sets the trigger element for the dialog.\n    ///\n    /// When a trigger is set, the dialog will render as a trigger button that opens the dialog when clicked.\n    pub fn trigger(mut self, trigger: impl IntoElement) -> Self {\n        self.trigger = Some(trigger.into_any_element());\n        self\n    }\n\n    /// Sets the content of the dialog.\n    pub fn content<F>(mut self, builder: F) -> Self\n    where\n        F: Fn(DialogContent, &mut Window, &mut App) -> DialogContent + 'static,\n    {\n        self.content_builder = Some(Rc::new(builder));\n        self\n    }\n\n    /// Sets the title of the dialog.\n    pub fn title(mut self, title: impl IntoElement) -> Self {\n        self.title = Some(title.into_any_element());\n        self\n    }\n\n    /// Sets the footer of the dialog, the footer will render at the bottom of the dialog, usually for action buttons.\n    ///\n    /// When you set the footer, the `button_props` will be ignored, you need to render the action buttons by yourself.\n    pub(crate) fn header(mut self, header: impl IntoElement) -> Self {\n        self.header = Some(header.into_any_element());\n        self\n    }\n\n    /// Sets the footer of the dialog, the footer will render at the bottom of the dialog, usually for action buttons.\n    ///\n    /// When you set the footer, the `button_props` will be ignored, you need to render the action buttons by yourself.\n    pub fn footer(mut self, footer: impl IntoElement) -> Self {\n        self.footer = Some(footer.into_any_element());\n        self\n    }\n\n    /// Set the button props of the dialog.\n    pub fn button_props(mut self, button_props: DialogButtonProps) -> Self {\n        self.button_props = button_props;\n        self\n    }\n\n    /// Sets the callback for when the dialog is closed.\n    ///\n    /// Called after [`Self::on_ok`] or [`Self::on_cancel`] callback.\n    pub fn on_close(\n        mut self,\n        on_close: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.button_props.on_close = Rc::new(on_close);\n        self\n    }\n\n    /// Sets the callback for when the dialog is has been confirmed.\n    ///\n    /// The callback should return `true` to close the dialog, if return `false` the dialog will not be closed.\n    pub fn on_ok(\n        mut self,\n        on_ok: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,\n    ) -> Self {\n        self.button_props = self.button_props.on_ok(on_ok);\n        self\n    }\n\n    /// Sets the callback for when the dialog is has been canceled.\n    ///\n    /// The callback should return `true` to close the dialog, if return `false` the dialog will not be closed.\n    pub fn on_cancel(\n        mut self,\n        on_cancel: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,\n    ) -> Self {\n        self.button_props = self.button_props.on_cancel(on_cancel);\n        self\n    }\n\n    /// Sets the false to hide close icon, default: true\n    pub fn close_button(mut self, close_button: bool) -> Self {\n        self.props.close_button = close_button;\n        self\n    }\n\n    /// Set the top offset of the dialog, defaults to None, will use the 1/10 of the viewport height.\n    pub fn margin_top(mut self, margin_top: impl Into<Pixels>) -> Self {\n        self.props.margin_top = Some(margin_top.into());\n        self\n    }\n\n    /// Sets the width of the dialog, defaults to 448px.\n    ///\n    /// See also [`Self::width`]\n    pub fn w(mut self, width: impl Into<Pixels>) -> Self {\n        self.props.width = width.into();\n        self\n    }\n\n    /// Sets the width of the dialog, defaults to 448px.\n    pub fn width(mut self, width: impl Into<Pixels>) -> Self {\n        self.props.width = width.into();\n        self\n    }\n\n    /// Set the maximum width of the dialog, defaults to `None`.\n    pub fn max_w(mut self, max_width: impl Into<Pixels>) -> Self {\n        self.props.max_width = Some(max_width.into());\n        self\n    }\n\n    /// Set the overlay of the dialog, defaults to `true`.\n    pub fn overlay(mut self, overlay: bool) -> Self {\n        self.props.overlay = overlay;\n        self\n    }\n\n    /// Set the overlay closable of the dialog, defaults to `true`.\n    ///\n    /// When the overlay is clicked, the dialog will be closed.\n    pub fn overlay_closable(mut self, overlay_closable: bool) -> Self {\n        self.props.overlay_closable = overlay_closable;\n        self\n    }\n\n    /// Set whether to support keyboard esc to close the dialog, defaults to `true`.\n    pub fn keyboard(mut self, keyboard: bool) -> Self {\n        self.props.keyboard = keyboard;\n        self\n    }\n\n    pub(crate) fn has_overlay(&self) -> bool {\n        self.props.overlay\n    }\n\n    pub(crate) fn with_props(mut self, props: DialogProps) -> Self {\n        self.props = props;\n        self\n    }\n\n    fn defer_close_dialog(window: &mut Window, cx: &mut App) {\n        Root::update(window, cx, |root, window, cx| {\n            root.defer_close_dialog(window, cx);\n        });\n    }\n}\n\nimpl ParentElement for Dialog {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Styled for Dialog {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Dialog {\n    fn render_trigger(self, trigger: AnyElement, _: &mut Window, _: &mut App) -> AnyElement {\n        let content_builder = self.content_builder.clone();\n        let style = self.style.clone();\n        let props = self.props.clone();\n        let button_props = self.button_props.clone();\n\n        div()\n            .on_mouse_down(MouseButton::Left, move |_, window, cx| {\n                let content_builder = content_builder.clone();\n                let style = style.clone();\n                let props = props.clone();\n                let button_props = button_props.clone();\n                window.open_dialog(cx, move |dialog, _, _| {\n                    dialog\n                        .refine_style(&style)\n                        .button_props(button_props.clone())\n                        .with_props(props.clone())\n                        .content({\n                            let content_builder = content_builder.clone();\n                            move |content, window, cx| {\n                                if let Some(builder) = content_builder.clone() {\n                                    builder(content, window, cx)\n                                } else {\n                                    content\n                                }\n                            }\n                        })\n                });\n                cx.stop_propagation();\n            })\n            .child(trigger)\n            .into_any_element()\n    }\n}\n\nimpl RenderOnce for Dialog {\n    fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        if let Some(trigger) = self.trigger.take() {\n            return self.render_trigger(trigger, window, cx);\n        }\n\n        let layer_ix = self.layer_ix;\n        let on_close = self.button_props.on_close.clone();\n        let on_ok = self.button_props.on_ok.clone();\n        let on_cancel = self.button_props.on_cancel.clone();\n\n        let window_paddings = crate::window_border::window_paddings(window);\n        let view_size = window.viewport_size()\n            - gpui::size(\n                window_paddings.left + window_paddings.right,\n                window_paddings.top + window_paddings.bottom,\n            );\n        let bounds = Bounds {\n            origin: Point::default(),\n            size: view_size,\n        };\n        let offset_top = px(layer_ix as f32 * 16.);\n        let y = self.props.margin_top.unwrap_or(view_size.height / 10.) + offset_top;\n        let x = bounds.center().x - self.props.width / 2.;\n\n        let base_size = window.text_style().font_size;\n        let rem_size = window.rem_size();\n\n        let mut paddings = Edges::all(px(16.));\n        if let Some(pl) = self.style.padding.left {\n            paddings.left = pl.to_pixels(base_size, rem_size);\n        }\n        if let Some(pr) = self.style.padding.right {\n            paddings.right = pr.to_pixels(base_size, rem_size);\n        }\n        if let Some(pt) = self.style.padding.top {\n            paddings.top = pt.to_pixels(base_size, rem_size);\n        }\n        if let Some(pb) = self.style.padding.bottom {\n            paddings.bottom = pb.to_pixels(base_size, rem_size);\n        }\n\n        let animation =\n            Animation::new(*ANIMATION_DURATION).with_easing(cubic_bezier(0.32, 0.72, 0., 1.));\n\n        anchored()\n            .position(point(window_paddings.left, window_paddings.top))\n            .snap_to_window()\n            .child(\n                div()\n                    .id(\"dialog\")\n                    .occlude()\n                    .w(view_size.width)\n                    .h(view_size.height)\n                    .when(self.props.overlay_visible, |this| {\n                        this.bg(overlay_color(self.props.overlay, cx))\n                    })\n                    .when(self.props.overlay, |this| {\n                        // Only the last dialog owns the `mouse down - close dialog` event.\n                        if (self.layer_ix + 1) != Root::read(window, cx).active_dialogs.len() {\n                            return this;\n                        }\n\n                        this.window_control_area(WindowControlArea::Drag)\n                            .on_any_mouse_down({\n                                let on_cancel = on_cancel.clone();\n                                let on_close = on_close.clone();\n                                move |event, window, cx| {\n                                    if event.position.y < TITLE_BAR_HEIGHT {\n                                        return;\n                                    }\n\n                                    cx.stop_propagation();\n                                    if self.props.overlay_closable\n                                        && event.button == MouseButton::Left\n                                    {\n                                        if on_cancel(&ClickEvent::default(), window, cx) {\n                                            on_close(&ClickEvent::default(), window, cx);\n                                            window.close_dialog(cx);\n                                        }\n                                    }\n                                }\n                            })\n                    })\n                    .child(\n                        v_flex()\n                            .id(layer_ix)\n                            .track_focus(&self.focus_handle)\n                            .focus_trap(format!(\"dialog-{}\", layer_ix), &self.focus_handle)\n                            .bg(cx.theme().background)\n                            .border_1()\n                            .border_color(cx.theme().border)\n                            .rounded(cx.theme().radius_lg)\n                            .min_h_24()\n                            .pt(paddings.top)\n                            .pb(paddings.bottom)\n                            .gap(paddings.top.max(px(8.)))\n                            .refine_style(&self.style)\n                            .px_0()\n                            .key_context(CONTEXT)\n                            .when(self.props.keyboard, |this| {\n                                this.on_action({\n                                    let on_cancel = on_cancel.clone();\n                                    let on_close = on_close.clone();\n                                    move |_: &CancelDialog, window, cx| {\n                                        // FIXME:\n                                        //\n                                        // Here some Dialog have no focus_handle, so it will not work will Escape key.\n                                        // But by now, we `cx.close_dialog()` going to close the last active model,\n                                        // so the Escape is unexpected to work.\n                                        if on_cancel(&ClickEvent::default(), window, cx) {\n                                            window.close_dialog(cx);\n                                            on_close(&ClickEvent::default(), window, cx);\n                                        }\n                                    }\n                                })\n                                .on_action({\n                                    let on_ok = on_ok.clone();\n                                    let on_close = on_close.clone();\n                                    move |_: &ConfirmDialog, window, cx| {\n                                        if on_ok(&ClickEvent::default(), window, cx) {\n                                            Self::defer_close_dialog(window, cx);\n                                            on_close(&ClickEvent::default(), window, cx);\n                                        }\n                                    }\n                                })\n                            })\n                            // There style is high priority, can't be overridden.\n                            .absolute()\n                            .occlude()\n                            .relative()\n                            .left(x)\n                            .top(y)\n                            .w(self.props.width)\n                            .when_some(self.props.max_width, |this, w| this.max_w(w))\n                            .child(\n                                v_flex()\n                                    .flex_1()\n                                    .overflow_hidden()\n                                    .gap_y_2()\n                                    .when_some(self.header, |this, header| {\n                                        this.child(\n                                            div()\n                                                .pl(paddings.left)\n                                                .pr(paddings.right)\n                                                .child(header),\n                                        )\n                                    })\n                                    .when_some(self.title, |this, title| {\n                                        this.child(\n                                            DialogTitle::new()\n                                                .pl(paddings.left)\n                                                .pr(paddings.right)\n                                                .child(title),\n                                        )\n                                    })\n                                    .when_some(self.content_builder, |this, builder| {\n                                        this.child(builder(\n                                            DialogContent::new()\n                                                .gap(paddings.bottom)\n                                                .pl(paddings.left)\n                                                .pr(paddings.right),\n                                            window,\n                                            cx,\n                                        ))\n                                    })\n                                    .when(!self.children.is_empty(), |this| {\n                                        this.child(\n                                            div().flex_1().overflow_hidden().child(\n                                                // Body\n                                                v_flex()\n                                                    .size_full()\n                                                    .overflow_y_scrollbar()\n                                                    .pl(paddings.left)\n                                                    .pr(paddings.right)\n                                                    .children(self.children),\n                                            ),\n                                        )\n                                    }),\n                            )\n                            .when_some(self.footer, |this, footer| {\n                                this.child(div().pl(paddings.left).pr(paddings.right).child(footer))\n                            })\n                            .children(self.props.close_button.then(|| {\n                                let top = (paddings.top - px(10.)).max(px(8.));\n                                let right = (paddings.right - px(10.)).max(px(8.));\n\n                                Button::new(\"close\")\n                                    .absolute()\n                                    .top(top)\n                                    .right(right)\n                                    .small()\n                                    .ghost()\n                                    .icon(IconName::Close)\n                                    .on_click({\n                                        let on_cancel = self.button_props.on_cancel.clone();\n                                        let on_close = self.button_props.on_close.clone();\n                                        move |_, window, cx| {\n                                            window.close_dialog(cx);\n                                            on_cancel(&ClickEvent::default(), window, cx);\n                                            on_close(&ClickEvent::default(), window, cx);\n                                        }\n                                    })\n                            }))\n                            .on_any_mouse_down({\n                                |_, _, cx| {\n                                    cx.stop_propagation();\n                                }\n                            })\n                            .with_animation(\"slide-down\", animation.clone(), move |this, delta| {\n                                // This is equivalent to `shadow_xl` with an extra opacity.\n                                let shadow = vec![\n                                    BoxShadow {\n                                        color: hsla(0., 0., 0., 0.1 * delta),\n                                        offset: point(px(0.), px(20.)),\n                                        blur_radius: px(25.),\n                                        spread_radius: px(-5.),\n                                    },\n                                    BoxShadow {\n                                        color: hsla(0., 0., 0., 0.1 * delta),\n                                        offset: point(px(0.), px(8.)),\n                                        blur_radius: px(10.),\n                                        spread_radius: px(-6.),\n                                    },\n                                ];\n                                this.top(y * delta).shadow(shadow)\n                            }),\n                    )\n                    .with_animation(\"fade-in\", animation, move |this, delta| this.opacity(delta)),\n            )\n            .into_any_element()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dialog/footer.rs",
    "content": "use gpui::{\n    AnyElement, App, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,\n    StatefulInteractiveElement, StyleRefinement, Styled, Window, div, relative,\n};\n\nuse crate::{\n    ActiveTheme as _, StyledExt as _,\n    dialog::{CancelDialog, ConfirmDialog},\n    h_flex,\n};\n\n/// Footer section of a dialog, typically contains action buttons.\n///\n/// # Examples\n///\n/// ```ignore\n/// DialogFooter::new()\n///     .child(DialogClose::new().child(Button::new(\"cancel\").label(\"Cancel\")))\n///     .child(Button::new(\"confirm\").label(\"Confirm\"))\n/// ```\n#[derive(IntoElement)]\npub struct DialogFooter {\n    style: StyleRefinement,\n    children: Vec<AnyElement>,\n}\n\nimpl DialogFooter {\n    pub fn new() -> Self {\n        Self { style: StyleRefinement::default(), children: Vec::new() }\n    }\n}\n\nimpl ParentElement for DialogFooter {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Styled for DialogFooter {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for DialogFooter {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        h_flex()\n            .gap_2()\n            .justify_end()\n            .line_height(relative(1.))\n            .rounded_b(cx.theme().radius_lg)\n            .refine_style(&self.style)\n            .children(self.children)\n    }\n}\n\npub trait DialogFooterButton {\n    fn is_cancel(&self) -> bool {\n        false\n    }\n\n    fn is_action(&self) -> bool {\n        false\n    }\n}\n\n#[derive(IntoElement)]\npub struct DialogClose {\n    children: Vec<AnyElement>,\n}\n\nimpl DialogClose {\n    pub fn new() -> Self {\n        Self { children: Vec::new() }\n    }\n}\n\nimpl ParentElement for DialogClose {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl RenderOnce for DialogClose {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        div()\n            .size_full()\n            .id(\"dialog-close\")\n            .on_click(move |_, window, cx| window.dispatch_action(Box::new(CancelDialog), cx))\n            .children(self.children)\n    }\n}\n\n#[derive(IntoElement)]\npub struct DialogAction {\n    children: Vec<AnyElement>,\n}\n\nimpl DialogAction {\n    pub fn new() -> Self {\n        Self { children: Vec::new() }\n    }\n}\n\nimpl ParentElement for DialogAction {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl RenderOnce for DialogAction {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        div()\n            .size_full()\n            .id(\"dialog-action\")\n            .on_click(move |_, window, cx| window.dispatch_action(Box::new(ConfirmDialog), cx))\n            .children(self.children)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dialog/header.rs",
    "content": "use gpui::{\n    AnyElement, App, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, Window,\n};\n\nuse crate::{StyledExt as _, v_flex};\n\n/// Header section of a dialog, typically contains DialogTitle and DialogDescription.\n///\n/// # Examples\n///\n/// ```ignore\n/// DialogHeader::new()\n///     .child(DialogTitle::new().child(\"Delete Account\"))\n///     .child(DialogDescription::new().child(\"This action cannot be undone.\"))\n/// ```\n#[derive(IntoElement)]\npub struct DialogHeader {\n    style: StyleRefinement,\n    children: Vec<AnyElement>,\n}\n\nimpl DialogHeader {\n    pub fn new() -> Self {\n        Self { style: StyleRefinement::default(), children: Vec::new() }\n    }\n}\n\nimpl ParentElement for DialogHeader {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Styled for DialogHeader {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for DialogHeader {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        v_flex().gap_2().refine_style(&self.style).children(self.children)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dialog/mod.rs",
    "content": "mod alert_dialog;\nmod content;\nmod description;\nmod dialog;\nmod footer;\nmod header;\nmod title;\n\npub use alert_dialog::*;\npub use content::DialogContent;\npub use description::DialogDescription;\npub use dialog::*;\npub use footer::*;\npub use header::DialogHeader;\npub use title::DialogTitle;\n"
  },
  {
    "path": "crates/ui/src/dialog/title.rs",
    "content": "use gpui::{\n    AnyElement, App, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,\n    StyleRefinement, Styled, Window, div, relative,\n};\n\nuse crate::StyledExt as _;\n\n/// Title element for a dialog header.\n#[derive(IntoElement)]\npub struct DialogTitle {\n    style: StyleRefinement,\n    children: Vec<AnyElement>,\n}\n\nimpl DialogTitle {\n    pub fn new() -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            children: vec![],\n        }\n    }\n}\n\nimpl ParentElement for DialogTitle {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Styled for DialogTitle {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for DialogTitle {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        div()\n            .id(\"dialog-title\")\n            .text_base()\n            .font_semibold()\n            .line_height(relative(1.))\n            .refine_style(&self.style)\n            .children(self.children)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/divider.rs",
    "content": "use crate::{ActiveTheme, StyledExt};\nuse gpui::{\n    App, Axis, Div, Hsla, IntoElement, ParentElement, PathBuilder, RenderOnce, SharedString,\n    StyleRefinement, Styled, Window, canvas, div, point, prelude::FluentBuilder as _, px,\n};\n\n/// The style of the divider line.\n#[derive(Clone, Copy, PartialEq, Default)]\npub enum DividerStyle {\n    #[default]\n    Solid,\n    Dashed,\n}\n\n/// A divider that can be either vertical or horizontal.\n#[derive(IntoElement)]\npub struct Divider {\n    base: Div,\n    style: StyleRefinement,\n    label: Option<SharedString>,\n    axis: Axis,\n    color: Option<Hsla>,\n    line_style: DividerStyle,\n}\n\nimpl Divider {\n    /// Creates a vertical divider.\n    pub fn vertical() -> Self {\n        Self {\n            base: div().h_full(),\n            axis: Axis::Vertical,\n            label: None,\n            color: None,\n            style: StyleRefinement::default(),\n            line_style: DividerStyle::Solid,\n        }\n    }\n\n    /// Creates a horizontal divider.\n    pub fn horizontal() -> Self {\n        Self {\n            base: div(),\n            axis: Axis::Horizontal,\n            label: None,\n            color: None,\n            style: StyleRefinement::default(),\n            line_style: DividerStyle::Solid,\n        }\n    }\n\n    /// Creates a vertical dashed divider.\n    pub fn vertical_dashed() -> Self {\n        Self::vertical().dashed()\n    }\n\n    /// Creates a horizontal dashed divider.\n    pub fn horizontal_dashed() -> Self {\n        Self::horizontal().dashed()\n    }\n\n    /// Sets the label for the divider.\n    pub fn label(mut self, label: impl Into<SharedString>) -> Self {\n        self.label = Some(label.into());\n        self\n    }\n\n    /// Sets the color for the divider line.\n    pub fn color(mut self, color: impl Into<Hsla>) -> Self {\n        self.color = Some(color.into());\n        self\n    }\n\n    /// Sets the style of the divider to dashed.\n    pub fn dashed(mut self) -> Self {\n        self.line_style = DividerStyle::Dashed;\n        self\n    }\n\n    fn render_base(axis: Axis) -> Div {\n        div().absolute().map(|this| match axis {\n            Axis::Vertical => this.w(px(1.)).h_full(),\n            Axis::Horizontal => this.h(px(1.)).w_full(),\n        })\n    }\n\n    fn render_solid(axis: Axis, color: Hsla) -> impl IntoElement {\n        Self::render_base(axis).bg(color)\n    }\n\n    fn render_dashed(axis: Axis, color: Hsla) -> impl IntoElement {\n        Self::render_base(axis).child(\n            canvas(\n                move |_, _, _| {},\n                move |bounds, _, window, _| {\n                    let mut builder = PathBuilder::stroke(px(1.)).dash_array(&[px(4.), px(2.)]);\n                    let (start, end) = match axis {\n                        Axis::Horizontal => {\n                            let x = bounds.origin.x;\n                            let y = bounds.origin.y + px(0.5);\n                            (point(x, y), point(x + bounds.size.width, y))\n                        }\n                        Axis::Vertical => {\n                            let x = bounds.origin.x + px(0.5);\n                            let y = bounds.origin.y;\n                            (point(x, y), point(x, y + bounds.size.height))\n                        }\n                    };\n                    builder.move_to(start);\n                    builder.line_to(end);\n                    if let Ok(line) = builder.build() {\n                        window.paint_path(line, color);\n                    }\n                },\n            )\n            .size_full(),\n        )\n    }\n}\n\nimpl Styled for Divider {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Divider {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let color = self.color.unwrap_or(cx.theme().border);\n        let axis = self.axis;\n        let line_style = self.line_style;\n\n        self.base\n            .flex()\n            .flex_shrink_0()\n            .items_center()\n            .justify_center()\n            .refine_style(&self.style)\n            .child(match line_style {\n                DividerStyle::Solid => Self::render_solid(axis, color).into_any_element(),\n                DividerStyle::Dashed => Self::render_dashed(axis, color).into_any_element(),\n            })\n            .when_some(self.label, |this, label| {\n                this.child(\n                    div()\n                        .px_2()\n                        .py_1()\n                        .mx_auto()\n                        .text_xs()\n                        .bg(cx.theme().background)\n                        .text_color(cx.theme().muted_foreground)\n                        .child(label),\n                )\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dock/dock.rs",
    "content": "//! Dock is a fixed container that places at left, bottom, right of the Windows.\n\nuse std::{ops::Deref, sync::Arc};\n\nuse gpui::{\n    App, AppContext, Axis, Context, Element, Empty, Entity, IntoElement, MouseMoveEvent,\n    MouseUpEvent, ParentElement as _, Pixels, Point, Render, Style, StyleRefinement, Styled as _,\n    WeakEntity, Window, div, prelude::FluentBuilder as _, px,\n};\nuse serde::{Deserialize, Serialize};\n\nuse crate::{\n    StyledExt,\n    resizable::{PANEL_MIN_SIZE, resize_handle},\n};\n\nuse super::{DockArea, DockItem, PanelView, TabPanel};\n\n#[derive(Clone)]\nstruct ResizePanel;\n\nimpl Render for ResizePanel {\n    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {\n        Empty\n    }\n}\n\n#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]\npub enum DockPlacement {\n    #[serde(rename = \"center\")]\n    Center,\n    #[serde(rename = \"left\")]\n    Left,\n    #[serde(rename = \"bottom\")]\n    Bottom,\n    #[serde(rename = \"right\")]\n    Right,\n}\n\nimpl DockPlacement {\n    fn axis(&self) -> Axis {\n        match self {\n            Self::Left | Self::Right => Axis::Horizontal,\n            Self::Bottom => Axis::Vertical,\n            Self::Center => unreachable!(),\n        }\n    }\n\n    pub fn is_left(&self) -> bool {\n        matches!(self, Self::Left)\n    }\n\n    pub fn is_bottom(&self) -> bool {\n        matches!(self, Self::Bottom)\n    }\n\n    pub fn is_right(&self) -> bool {\n        matches!(self, Self::Right)\n    }\n}\n\n/// The Dock is a fixed container that places at left, bottom, right of the Windows.\n///\n/// This is unlike Panel, it can't be move or add any other panel.\npub struct Dock {\n    pub(super) placement: DockPlacement,\n    dock_area: WeakEntity<DockArea>,\n    pub(crate) panel: DockItem,\n    /// The size is means the width or height of the Dock, if the placement is left or right, the size is width, otherwise the size is height.\n    pub(super) size: Pixels,\n    pub(super) open: bool,\n    /// Whether the Dock is collapsible, default: true\n    pub(super) collapsible: bool,\n\n    // Runtime state\n    /// Whether the Dock is resizing\n    resizing: bool,\n}\n\nimpl Dock {\n    pub(crate) fn new(\n        dock_area: WeakEntity<DockArea>,\n        placement: DockPlacement,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Self {\n        let panel = cx.new(|cx| {\n            let mut tab = TabPanel::new(None, dock_area.clone(), window, cx);\n            tab.closable = false;\n            tab\n        });\n\n        let panel = DockItem::Tabs {\n            size: None,\n            items: Vec::new(),\n            active_ix: 0,\n            view: panel.clone(),\n        };\n\n        Self::subscribe_panel_events(dock_area.clone(), &panel, window, cx);\n\n        Self {\n            placement,\n            dock_area,\n            panel,\n            open: true,\n            collapsible: true,\n            size: px(200.0),\n            resizing: false,\n        }\n    }\n\n    pub fn left(\n        dock_area: WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Self {\n        Self::new(dock_area, DockPlacement::Left, window, cx)\n    }\n\n    pub fn bottom(\n        dock_area: WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Self {\n        Self::new(dock_area, DockPlacement::Bottom, window, cx)\n    }\n\n    pub fn right(\n        dock_area: WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Self {\n        Self::new(dock_area, DockPlacement::Right, window, cx)\n    }\n\n    /// Update the Dock to be collapsible or not.\n    ///\n    /// And if the Dock is not collapsible, it will be open.\n    pub fn set_collapsible(&mut self, collapsible: bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.collapsible = collapsible;\n        if !collapsible {\n            self.open = true\n        }\n        cx.notify();\n    }\n\n    pub(super) fn from_state(\n        dock_area: WeakEntity<DockArea>,\n        placement: DockPlacement,\n        size: Pixels,\n        panel: DockItem,\n        open: bool,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Self {\n        Self::subscribe_panel_events(dock_area.clone(), &panel, window, cx);\n\n        if !open {\n            match panel.clone() {\n                DockItem::Tabs { view, .. } => {\n                    view.update(cx, |panel, cx| {\n                        panel.set_collapsed(true, window, cx);\n                    });\n                }\n                DockItem::Split { items, .. } => {\n                    for item in items {\n                        item.set_collapsed(true, window, cx);\n                    }\n                }\n                _ => {}\n            }\n        }\n\n        Self {\n            placement,\n            dock_area,\n            panel,\n            open,\n            size,\n            collapsible: true,\n            resizing: false,\n        }\n    }\n\n    fn subscribe_panel_events(\n        dock_area: WeakEntity<DockArea>,\n        panel: &DockItem,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        match panel {\n            DockItem::Tabs { view, .. } => {\n                window.defer(cx, {\n                    let view = view.clone();\n                    move |window, cx| {\n                        _ = dock_area.update(cx, |this, cx| {\n                            this.subscribe_panel(&view, window, cx);\n                        });\n                    }\n                });\n            }\n            DockItem::Split { items, view, .. } => {\n                for item in items {\n                    Self::subscribe_panel_events(dock_area.clone(), item, window, cx);\n                }\n                window.defer(cx, {\n                    let view = view.clone();\n                    move |window, cx| {\n                        _ = dock_area.update(cx, |this, cx| {\n                            this.subscribe_panel(&view, window, cx);\n                        });\n                    }\n                });\n            }\n            DockItem::Tiles { view, .. } => {\n                window.defer(cx, {\n                    let view = view.clone();\n                    move |window, cx| {\n                        _ = dock_area.update(cx, |this, cx| {\n                            this.subscribe_panel(&view, window, cx);\n                        });\n                    }\n                });\n            }\n            DockItem::Panel { .. } => {\n                // Not supported\n            }\n        }\n    }\n\n    pub fn set_panel(&mut self, panel: DockItem, _: &mut Window, cx: &mut Context<Self>) {\n        self.panel = panel;\n        cx.notify();\n    }\n\n    pub fn panel(&self) -> &DockItem {\n        &self.panel\n    }\n\n    pub fn is_open(&self) -> bool {\n        self.open\n    }\n\n    pub fn toggle_open(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        self.set_open(!self.open, window, cx);\n    }\n\n    /// Returns the size of the Dock, the size is means the width or height of\n    /// the Dock, if the placement is left or right, the size is width,\n    /// otherwise the size is height.\n    pub fn size(&self) -> Pixels {\n        self.size\n    }\n\n    /// Set the size of the Dock.\n    pub fn set_size(&mut self, size: Pixels, _: &mut Window, cx: &mut Context<Self>) {\n        self.size = size.max(PANEL_MIN_SIZE);\n        cx.notify();\n    }\n\n    /// Set the open state of the Dock.\n    pub fn set_open(&mut self, open: bool, window: &mut Window, cx: &mut Context<Self>) {\n        self.open = open;\n        let item = self.panel.clone();\n        cx.defer_in(window, move |_, window, cx| {\n            item.set_collapsed(!open, window, cx);\n        });\n        cx.notify();\n    }\n\n    /// Add item to the Dock.\n    pub fn add_panel(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.panel\n            .add_panel(panel, &self.dock_area, None, window, cx);\n        cx.notify();\n    }\n\n    /// Remove item from the Dock.\n    pub fn remove_panel(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.panel.remove_panel(panel, window, cx);\n        cx.notify();\n    }\n\n    fn render_resize_handle(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let axis = self.placement.axis();\n        let view = cx.entity().clone();\n\n        resize_handle(\"resize-handle\", axis)\n            .placement(self.placement)\n            .on_drag(ResizePanel {}, move |info, _, _, cx| {\n                cx.stop_propagation();\n                view.update(cx, |view, _| {\n                    view.resizing = true;\n                });\n                cx.new(|_| info.deref().clone())\n            })\n    }\n    fn resize(&mut self, mouse_position: Point<Pixels>, _: &mut Window, cx: &mut Context<Self>) {\n        if !self.resizing {\n            return;\n        }\n\n        let dock_area = self\n            .dock_area\n            .upgrade()\n            .expect(\"DockArea is missing\")\n            .read(cx);\n        let area_bounds = dock_area.bounds;\n        let mut left_dock_size = px(0.0);\n        let mut right_dock_size = px(0.0);\n\n        // Get the size of the left dock if it's open and not the current dock\n        if let Some(left_dock) = &dock_area.left_dock {\n            if left_dock.entity_id() != cx.entity().entity_id() {\n                let left_dock_read = left_dock.read(cx);\n                if left_dock_read.is_open() {\n                    left_dock_size = left_dock_read.size;\n                }\n            }\n        }\n\n        // Get the size of the right dock if it's open and not the current dock\n        if let Some(right_dock) = &dock_area.right_dock {\n            if right_dock.entity_id() != cx.entity().entity_id() {\n                let right_dock_read = right_dock.read(cx);\n                if right_dock_read.is_open() {\n                    right_dock_size = right_dock_read.size;\n                }\n            }\n        }\n\n        let size = match self.placement {\n            DockPlacement::Left => mouse_position.x - area_bounds.left(),\n            DockPlacement::Right => area_bounds.right() - mouse_position.x,\n            DockPlacement::Bottom => area_bounds.bottom() - mouse_position.y,\n            DockPlacement::Center => unreachable!(),\n        };\n        match self.placement {\n            DockPlacement::Left => {\n                let max_size = area_bounds.size.width - PANEL_MIN_SIZE - right_dock_size;\n                self.size = size.clamp(PANEL_MIN_SIZE, max_size);\n            }\n            DockPlacement::Right => {\n                let max_size = area_bounds.size.width - PANEL_MIN_SIZE - left_dock_size;\n                self.size = size.clamp(PANEL_MIN_SIZE, max_size);\n            }\n            DockPlacement::Bottom => {\n                let max_size = area_bounds.size.height - PANEL_MIN_SIZE;\n                self.size = size.clamp(PANEL_MIN_SIZE, max_size);\n            }\n            DockPlacement::Center => unreachable!(),\n        }\n\n        cx.notify();\n    }\n\n    fn done_resizing(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {\n        self.resizing = false;\n    }\n}\n\nimpl Render for Dock {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {\n        if !self.open && !self.placement.is_bottom() {\n            return div();\n        }\n\n        let cache_style = StyleRefinement::default().absolute().size_full();\n\n        div()\n            .relative()\n            .overflow_hidden()\n            .map(|this| match self.placement {\n                DockPlacement::Left | DockPlacement::Right => this.h_flex().h_full().w(self.size),\n                DockPlacement::Bottom => this.w_full().h(self.size),\n                DockPlacement::Center => unreachable!(),\n            })\n            // Bottom Dock should keep the title bar, then user can click the Toggle button\n            .when(!self.open && self.placement.is_bottom(), |this| {\n                this.h(px(29.))\n            })\n            .map(|this| match &self.panel {\n                DockItem::Split { view, .. } => this.child(view.clone()),\n                DockItem::Tabs { view, .. } => this.child(view.clone()),\n                DockItem::Panel { view, .. } => this.child(view.clone().view().cached(cache_style)),\n                // Not support to render Tiles and Tile into Dock\n                DockItem::Tiles { .. } => this,\n            })\n            .child(self.render_resize_handle(window, cx))\n            .child(DockElement {\n                view: cx.entity().clone(),\n            })\n    }\n}\n\nstruct DockElement {\n    view: Entity<Dock>,\n}\n\nimpl IntoElement for DockElement {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\nimpl Element for DockElement {\n    type RequestLayoutState = ();\n    type PrepaintState = ();\n\n    fn id(&self) -> Option<gpui::ElementId> {\n        None\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        _: Option<&gpui::GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        window: &mut gpui::Window,\n        cx: &mut App,\n    ) -> (gpui::LayoutId, Self::RequestLayoutState) {\n        (window.request_layout(Style::default(), None, cx), ())\n    }\n\n    fn prepaint(\n        &mut self,\n        _: Option<&gpui::GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        _: gpui::Bounds<Pixels>,\n        _: &mut Self::RequestLayoutState,\n        _window: &mut gpui::Window,\n        _cx: &mut App,\n    ) -> Self::PrepaintState {\n        ()\n    }\n\n    fn paint(\n        &mut self,\n        _: Option<&gpui::GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        _: gpui::Bounds<Pixels>,\n        _: &mut Self::RequestLayoutState,\n        _: &mut Self::PrepaintState,\n        window: &mut gpui::Window,\n        cx: &mut App,\n    ) {\n        window.on_mouse_event({\n            let view = self.view.clone();\n            let resizing = view.read(cx).resizing;\n            move |e: &MouseMoveEvent, phase, window, cx| {\n                if !resizing {\n                    return;\n                }\n                if !phase.bubble() {\n                    return;\n                }\n\n                view.update(cx, |view, cx| view.resize(e.position, window, cx))\n            }\n        });\n\n        // When any mouse up, stop dragging\n        window.on_mouse_event({\n            let view = self.view.clone();\n            move |_: &MouseUpEvent, phase, window, cx| {\n                if phase.bubble() {\n                    view.update(cx, |view, cx| view.done_resizing(window, cx));\n                }\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dock/invalid_panel.rs",
    "content": "use gpui::{\n    App, EventEmitter, FocusHandle, Focusable, ParentElement as _, Render, SharedString,\n    Styled as _, Window,\n};\n\nuse crate::ActiveTheme as _;\n\nuse super::{Panel, PanelEvent, PanelState};\n\npub(crate) struct InvalidPanel {\n    name: SharedString,\n    focus_handle: FocusHandle,\n    old_state: PanelState,\n}\n\nimpl InvalidPanel {\n    pub(crate) fn new(name: &str, state: PanelState, _: &mut Window, cx: &mut App) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            name: SharedString::from(name.to_owned()),\n            old_state: state,\n        }\n    }\n}\nimpl Panel for InvalidPanel {\n    fn panel_name(&self) -> &'static str {\n        \"InvalidPanel\"\n    }\n\n    fn dump(&self, _cx: &App) -> super::PanelState {\n        self.old_state.clone()\n    }\n}\nimpl EventEmitter<PanelEvent> for InvalidPanel {}\nimpl Focusable for InvalidPanel {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\nimpl Render for InvalidPanel {\n    fn render(\n        &mut self,\n        _: &mut gpui::Window,\n        cx: &mut gpui::Context<Self>,\n    ) -> impl gpui::IntoElement {\n        gpui::div()\n            .size_full()\n            .my_6()\n            .flex()\n            .flex_col()\n            .items_center()\n            .justify_center()\n            .text_color(cx.theme().muted_foreground)\n            .child(format!(\n                \"The `{}` panel type is not registered in PanelRegistry.\",\n                self.name.clone()\n            ))\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dock/mod.rs",
    "content": "mod dock;\nmod invalid_panel;\nmod panel;\nmod stack_panel;\nmod state;\nmod tab_panel;\nmod tiles;\n\nuse anyhow::Result;\nuse gpui::{\n    AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Edges, Entity, EntityId,\n    EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _, Pixels, Render,\n    SharedString, Styled, Subscription, WeakEntity, Window, actions, div, prelude::FluentBuilder,\n};\nuse std::sync::Arc;\n\npub use dock::*;\npub use panel::*;\npub use stack_panel::*;\npub use state::*;\npub use tab_panel::*;\npub use tiles::*;\n\nuse crate::ElementExt;\n\npub(crate) fn init(cx: &mut App) {\n    PanelRegistry::init(cx);\n}\n\nactions!(dock, [ToggleZoom, ClosePanel]);\n\npub enum DockEvent {\n    /// The layout of the dock has changed, subscribers this to save the layout.\n    ///\n    /// This event is emitted when every time the layout of the dock has changed,\n    /// So it emits may be too frequently, you may want to debounce the event.\n    LayoutChanged,\n\n    /// The drag item drop event.\n    DragDrop(AnyDrag),\n}\n\n/// The main area of the dock.\npub struct DockArea {\n    id: SharedString,\n    /// The version is used to special the default layout, this is like the `panel_version` in [`Panel`](Panel).\n    version: Option<usize>,\n    pub(crate) bounds: Bounds<Pixels>,\n\n    /// The center view of the dock_area.\n    center: DockItem,\n    /// The left dock of the dock_area.\n    left_dock: Option<Entity<Dock>>,\n    /// The bottom dock of the dock_area.\n    bottom_dock: Option<Entity<Dock>>,\n    /// The right dock of the dock_area.\n    right_dock: Option<Entity<Dock>>,\n\n    /// The entity_id of the [`TabPanel`](TabPanel) where each toggle button should be displayed,\n    toggle_button_panels: Edges<Option<EntityId>>,\n\n    /// Whether to show the toggle button.\n    toggle_button_visible: bool,\n    /// The top zoom view of the dock_area, if any.\n    zoom_view: Option<AnyView>,\n\n    /// Lock panels layout, but allow to resize.\n    locked: bool,\n\n    /// The panel style, default is [`PanelStyle::Default`](PanelStyle::Default).\n    pub(crate) panel_style: PanelStyle,\n\n    _subscriptions: Vec<Subscription>,\n}\n\n/// DockItem is a tree structure that represents the layout of the dock.\n#[derive(Clone)]\npub enum DockItem {\n    /// Split layout\n    Split {\n        axis: Axis,\n        /// Self size, only used for build split panels\n        size: Option<Pixels>,\n        items: Vec<DockItem>,\n        /// Items sizes\n        sizes: Vec<Option<Pixels>>,\n        view: Entity<StackPanel>,\n    },\n    /// Tab layout\n    Tabs {\n        /// Self size, only used for build split panels\n        size: Option<Pixels>,\n        items: Vec<Arc<dyn PanelView>>,\n        active_ix: usize,\n        view: Entity<TabPanel>,\n    },\n    /// Panel layout\n    Panel {\n        /// Self size, only used for build split panels\n        size: Option<Pixels>,\n        view: Arc<dyn PanelView>,\n    },\n    /// Tiles layout\n    Tiles {\n        /// Self size, only used for build split panels\n        size: Option<Pixels>,\n        items: Vec<TileItem>,\n        view: Entity<Tiles>,\n    },\n}\n\nimpl std::fmt::Debug for DockItem {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            DockItem::Split {\n                axis, items, sizes, ..\n            } => f\n                .debug_struct(\"Split\")\n                .field(\"axis\", axis)\n                .field(\"items\", &items.len())\n                .field(\"sizes\", sizes)\n                .finish(),\n            DockItem::Tabs {\n                items, active_ix, ..\n            } => f\n                .debug_struct(\"Tabs\")\n                .field(\"items\", &items.len())\n                .field(\"active_ix\", active_ix)\n                .finish(),\n            DockItem::Panel { .. } => f.debug_struct(\"Panel\").finish(),\n            DockItem::Tiles { .. } => f.debug_struct(\"Tiles\").finish(),\n        }\n    }\n}\n\nimpl DockItem {\n    /// Get the size of the DockItem.\n    fn get_size(&self) -> Option<Pixels> {\n        match self {\n            Self::Split { size, .. } => *size,\n            Self::Tabs { size, .. } => *size,\n            Self::Panel { size, .. } => *size,\n            Self::Tiles { size, .. } => *size,\n        }\n    }\n\n    /// Set size for the DockItem.\n    pub fn size(mut self, new_size: impl Into<Pixels>) -> Self {\n        let new_size: Option<Pixels> = Some(new_size.into());\n        match self {\n            Self::Split { ref mut size, .. } => *size = new_size,\n            Self::Tabs { ref mut size, .. } => *size = new_size,\n            Self::Tiles { ref mut size, .. } => *size = new_size,\n            Self::Panel { ref mut size, .. } => *size = new_size,\n        }\n        self\n    }\n\n    /// Set active index for the DockItem, only valid for [`DockItem::Tabs`].\n    pub fn active_index(mut self, new_active_ix: usize, cx: &mut App) -> Self {\n        debug_assert!(\n            matches!(self, Self::Tabs { .. }),\n            \"active_ix can only be set for DockItem::Tabs\"\n        );\n\n        if let Self::Tabs {\n            ref mut active_ix,\n            ref mut view,\n            ..\n        } = self\n        {\n            *active_ix = new_active_ix;\n            view.update(cx, |tab_panel, _| {\n                tab_panel.active_ix = new_active_ix;\n            });\n        }\n        self\n    }\n\n    /// Create DockItem::Split with given split layout.\n    pub fn split(\n        axis: Axis,\n        items: Vec<DockItem>,\n        dock_area: &WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self {\n        let sizes = items.iter().map(|item| item.get_size()).collect();\n        Self::split_with_sizes(axis, items, sizes, dock_area, window, cx)\n    }\n\n    /// Create DockItem with vertical split layout.\n    pub fn v_split(\n        items: Vec<DockItem>,\n        dock_area: &WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self {\n        Self::split(Axis::Vertical, items, dock_area, window, cx)\n    }\n\n    /// Create DockItem with horizontal split layout.\n    pub fn h_split(\n        items: Vec<DockItem>,\n        dock_area: &WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self {\n        Self::split(Axis::Horizontal, items, dock_area, window, cx)\n    }\n\n    /// Create DockItem with split layout, each item of panel have specified size.\n    ///\n    /// Please note that the `items` and `sizes` must have the same length.\n    /// Set `None` in `sizes` to make the index of panel have auto size.\n    pub fn split_with_sizes(\n        axis: Axis,\n        items: Vec<DockItem>,\n        sizes: Vec<Option<Pixels>>,\n        dock_area: &WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self {\n        let mut items = items;\n        let stack_panel = cx.new(|cx| {\n            let mut stack_panel = StackPanel::new(axis, window, cx);\n            for (i, item) in items.iter_mut().enumerate() {\n                let view = item.view();\n                let size = sizes.get(i).copied().flatten();\n                stack_panel.add_panel(view.clone(), size, dock_area.clone(), window, cx)\n            }\n\n            for (i, item) in items.iter().enumerate() {\n                let view = item.view();\n                let size = sizes.get(i).copied().flatten();\n                stack_panel.add_panel(view.clone(), size, dock_area.clone(), window, cx)\n            }\n\n            stack_panel\n        });\n\n        window.defer(cx, {\n            let stack_panel = stack_panel.clone();\n            let dock_area = dock_area.clone();\n            move |window, cx| {\n                _ = dock_area.update(cx, |this, cx| {\n                    this.subscribe_panel(&stack_panel, window, cx);\n                });\n            }\n        });\n\n        Self::Split {\n            axis,\n            size: None,\n            items,\n            sizes,\n            view: stack_panel,\n        }\n    }\n\n    /// Create DockItem with panel layout\n    pub fn panel(panel: Arc<dyn PanelView>) -> Self {\n        Self::Panel {\n            size: None,\n            view: panel,\n        }\n    }\n\n    /// Create DockItem with tiles layout\n    ///\n    /// This items and metas should have the same length.\n    pub fn tiles(\n        items: Vec<DockItem>,\n        metas: Vec<impl Into<TileMeta> + Copy>,\n        dock_area: &WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self {\n        assert!(items.len() == metas.len());\n\n        let tile_panel = cx.new(|cx| {\n            let mut tiles = Tiles::new(window, cx);\n            for (ix, item) in items.clone().into_iter().enumerate() {\n                match item {\n                    DockItem::Tabs { view, .. } => {\n                        let meta: TileMeta = metas[ix].into();\n                        let tile_item =\n                            TileItem::new(Arc::new(view), meta.bounds).z_index(meta.z_index);\n                        tiles.add_item(tile_item, dock_area, window, cx);\n                    }\n                    DockItem::Panel { view, .. } => {\n                        let meta: TileMeta = metas[ix].into();\n                        let tile_item =\n                            TileItem::new(view.clone(), meta.bounds).z_index(meta.z_index);\n                        tiles.add_item(tile_item, dock_area, window, cx);\n                    }\n                    _ => {\n                        // Ignore non-tabs items\n                    }\n                }\n            }\n            tiles\n        });\n\n        window.defer(cx, {\n            let tile_panel = tile_panel.clone();\n            let dock_area = dock_area.clone();\n            move |window, cx| {\n                _ = dock_area.update(cx, |this, cx| {\n                    this.subscribe_panel(&tile_panel, window, cx);\n                    this.subscribe_tiles_item_drop(&tile_panel, window, cx);\n                });\n            }\n        });\n\n        Self::Tiles {\n            size: None,\n            items: tile_panel.read(cx).panels.clone(),\n            view: tile_panel,\n        }\n    }\n\n    /// Create DockItem with tabs layout, items are displayed as tabs.\n    ///\n    /// The `active_ix` is the index of the active tab, if `None` the first tab is active.\n    pub fn tabs(\n        items: Vec<Arc<dyn PanelView>>,\n        dock_area: &WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self {\n        let mut new_items: Vec<Arc<dyn PanelView>> = vec![];\n        for item in items.into_iter() {\n            new_items.push(item)\n        }\n        Self::new_tabs(new_items, None, dock_area, window, cx)\n    }\n\n    pub fn tab<P: Panel>(\n        item: Entity<P>,\n        dock_area: &WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self {\n        Self::new_tabs(vec![Arc::new(item.clone())], None, dock_area, window, cx)\n    }\n\n    fn new_tabs(\n        items: Vec<Arc<dyn PanelView>>,\n        active_ix: Option<usize>,\n        dock_area: &WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self {\n        let active_ix = active_ix.unwrap_or(0);\n        let tab_panel = cx.new(|cx| {\n            let mut tab_panel = TabPanel::new(None, dock_area.clone(), window, cx);\n            for item in items.iter() {\n                tab_panel.add_panel(item.clone(), window, cx)\n            }\n            tab_panel.active_ix = active_ix;\n            tab_panel\n        });\n\n        Self::Tabs {\n            size: None,\n            items,\n            active_ix,\n            view: tab_panel,\n        }\n    }\n\n    /// Returns the views of the dock item.\n    pub fn view(&self) -> Arc<dyn PanelView> {\n        match self {\n            Self::Split { view, .. } => Arc::new(view.clone()),\n            Self::Tabs { view, .. } => Arc::new(view.clone()),\n            Self::Tiles { view, .. } => Arc::new(view.clone()),\n            Self::Panel { view, .. } => view.clone(),\n        }\n    }\n\n    /// Find existing panel in the dock item.\n    pub fn find_panel(&self, panel: Arc<dyn PanelView>) -> Option<Arc<dyn PanelView>> {\n        match self {\n            Self::Split { items, .. } => {\n                items.iter().find_map(|item| item.find_panel(panel.clone()))\n            }\n            Self::Tabs { items, .. } => items.iter().find(|item| *item == &panel).cloned(),\n            Self::Panel { view, .. } => Some(view.clone()),\n            Self::Tiles { items, .. } => items.iter().find_map(|item| {\n                if &item.panel == &panel {\n                    Some(item.panel.clone())\n                } else {\n                    None\n                }\n            }),\n        }\n    }\n\n    /// Add a panel to the dock item.\n    pub fn add_panel(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        dock_area: &WeakEntity<DockArea>,\n        bounds: Option<Bounds<Pixels>>,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        match self {\n            Self::Tabs { view, items, .. } => {\n                items.push(panel.clone());\n                view.update(cx, |tab_panel, cx| {\n                    tab_panel.add_panel(panel, window, cx);\n                });\n            }\n            Self::Split { view, items, .. } => {\n                // Iter items to add panel to the first tabs\n                for item in items.into_iter() {\n                    if let DockItem::Tabs { view, .. } = item {\n                        view.update(cx, |tab_panel, cx| {\n                            tab_panel.add_panel(panel.clone(), window, cx);\n                        });\n                        return;\n                    }\n                }\n\n                // Unable to find tabs, create new tabs\n                let new_item = Self::tabs(vec![panel.clone()], dock_area, window, cx);\n                items.push(new_item.clone());\n                view.update(cx, |stack_panel, cx| {\n                    stack_panel.add_panel(new_item.view(), None, dock_area.clone(), window, cx);\n                });\n            }\n            Self::Tiles { view, items, .. } => {\n                let tile_item = TileItem::new(\n                    Arc::new(cx.new(|cx| {\n                        let mut tab_panel = TabPanel::new(None, dock_area.clone(), window, cx);\n                        tab_panel.add_panel(panel.clone(), window, cx);\n                        tab_panel\n                    })),\n                    bounds.unwrap_or_else(|| TileMeta::default().bounds),\n                );\n\n                items.push(tile_item.clone());\n                view.update(cx, |tiles, cx| {\n                    tiles.add_item(tile_item, dock_area, window, cx);\n                });\n            }\n            Self::Panel { .. } => {}\n        }\n    }\n\n    /// Remove a panel from the dock item.\n    pub fn remove_panel(&self, panel: Arc<dyn PanelView>, window: &mut Window, cx: &mut App) {\n        match self {\n            DockItem::Tabs { view, .. } => {\n                view.update(cx, |tab_panel, cx| {\n                    tab_panel.remove_panel(panel, window, cx);\n                });\n            }\n            DockItem::Split { items, view, .. } => {\n                // For each child item, set collapsed state\n                for item in items {\n                    item.remove_panel(panel.clone(), window, cx);\n                }\n                view.update(cx, |split, cx| {\n                    split.remove_panel(panel, window, cx);\n                });\n            }\n            DockItem::Tiles { view, .. } => {\n                view.update(cx, |tiles, cx| {\n                    tiles.remove(panel, window, cx);\n                });\n            }\n            DockItem::Panel { .. } => {}\n        }\n    }\n\n    pub fn set_collapsed(&self, collapsed: bool, window: &mut Window, cx: &mut App) {\n        match self {\n            DockItem::Tabs { view, .. } => {\n                view.update(cx, |tab_panel, cx| {\n                    tab_panel.set_collapsed(collapsed, window, cx);\n                });\n            }\n            DockItem::Split { items, .. } => {\n                // For each child item, set collapsed state\n                for item in items {\n                    item.set_collapsed(collapsed, window, cx);\n                }\n            }\n            DockItem::Tiles { .. } => {}\n            DockItem::Panel { view, .. } => view.set_active(!collapsed, window, cx),\n        }\n    }\n\n    /// Recursively traverses to find the left-most and top-most TabPanel.\n    pub(crate) fn left_top_tab_panel(&self, cx: &App) -> Option<Entity<TabPanel>> {\n        match self {\n            DockItem::Tabs { view, .. } => Some(view.clone()),\n            DockItem::Split { view, .. } => view.read(cx).left_top_tab_panel(true, cx),\n            DockItem::Tiles { .. } => None,\n            DockItem::Panel { .. } => None,\n        }\n    }\n\n    /// Recursively traverses to find the right-most and top-most TabPanel.\n    pub(crate) fn right_top_tab_panel(&self, cx: &App) -> Option<Entity<TabPanel>> {\n        match self {\n            DockItem::Tabs { view, .. } => Some(view.clone()),\n            DockItem::Split { view, .. } => view.read(cx).right_top_tab_panel(true, cx),\n            DockItem::Tiles { .. } => None,\n            DockItem::Panel { .. } => None,\n        }\n    }\n}\n\nimpl DockArea {\n    pub fn new(\n        id: impl Into<SharedString>,\n        version: Option<usize>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Self {\n        let stack_panel = cx.new(|cx| StackPanel::new(Axis::Horizontal, window, cx));\n\n        let dock_item = DockItem::Split {\n            axis: Axis::Horizontal,\n            size: None,\n            items: vec![],\n            sizes: vec![],\n            view: stack_panel.clone(),\n        };\n\n        let mut this = Self {\n            id: id.into(),\n            version,\n            bounds: Bounds::default(),\n            center: dock_item,\n            left_dock: None,\n            right_dock: None,\n            bottom_dock: None,\n            zoom_view: None,\n            toggle_button_panels: Edges::default(),\n            toggle_button_visible: true,\n            locked: false,\n            panel_style: PanelStyle::default(),\n            _subscriptions: vec![],\n        };\n\n        this.subscribe_panel(&stack_panel, window, cx);\n\n        this\n    }\n\n    /// Return the bounds of the dock area.\n    pub fn bounds(&self) -> Bounds<Pixels> {\n        self.bounds\n    }\n\n    /// Subscribe to the tiles item drag item drop event\n    fn subscribe_tiles_item_drop(\n        &mut self,\n        tile_panel: &Entity<Tiles>,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self._subscriptions\n            .push(cx.subscribe(tile_panel, move |_, _, evt: &DragDrop, cx| {\n                let item = evt.0.clone();\n                cx.emit(DockEvent::DragDrop(item));\n            }));\n    }\n\n    /// Set the panel style of the dock area.\n    pub fn panel_style(mut self, style: PanelStyle) -> Self {\n        self.panel_style = style;\n        self\n    }\n\n    /// Set version of the dock area.\n    pub fn set_version(&mut self, version: usize, _: &mut Window, cx: &mut Context<Self>) {\n        self.version = Some(version);\n        cx.notify();\n    }\n\n    /// Return the center dock item.\n    pub fn center(&self) -> &DockItem {\n        &self.center\n    }\n\n    /// Return the left dock item.\n    pub fn left_dock(&self) -> Option<&Entity<Dock>> {\n        self.left_dock.as_ref()\n    }\n\n    /// Return the bottom dock item.\n    pub fn bottom_dock(&self) -> Option<&Entity<Dock>> {\n        self.bottom_dock.as_ref()\n    }\n\n    /// Return the right dock item.\n    pub fn right_dock(&self) -> Option<&Entity<Dock>> {\n        self.right_dock.as_ref()\n    }\n\n    /// Remove the left dock.\n    pub fn remove_left_dock(&mut self, _: &mut Window, _: &mut Context<Self>) {\n        self.left_dock = None;\n    }\n\n    /// Remove the bottom dock.\n    pub fn remove_bottom_dock(&mut self, _: &mut Window, _: &mut Context<Self>) {\n        self.bottom_dock = None;\n    }\n\n    /// Remove the right dock.\n    pub fn remove_right_dock(&mut self, _: &mut Window, _: &mut Context<Self>) {\n        self.right_dock = None;\n    }\n\n    /// The the DockItem as the center of the dock area.\n    ///\n    /// This is used to render at the Center of the DockArea.\n    pub fn set_center(&mut self, center: DockItem, window: &mut Window, cx: &mut Context<Self>) {\n        self.subscribe_item(&center, window, cx);\n        self.center = center;\n        self.update_toggle_button_tab_panels(window, cx);\n        cx.notify();\n    }\n\n    pub fn set_left_dock(\n        &mut self,\n        panel: DockItem,\n        size: Option<Pixels>,\n        open: bool,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.subscribe_item(&panel, window, cx);\n        let weak_self = cx.entity().downgrade();\n        self.left_dock = Some(cx.new(|cx| {\n            let mut dock = Dock::left(weak_self.clone(), window, cx);\n            if let Some(size) = size {\n                dock.set_size(size, window, cx);\n            }\n            dock.set_panel(panel, window, cx);\n            dock.set_open(open, window, cx);\n            dock\n        }));\n        self.update_toggle_button_tab_panels(window, cx);\n    }\n\n    pub fn set_bottom_dock(\n        &mut self,\n        panel: DockItem,\n        size: Option<Pixels>,\n        open: bool,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.subscribe_item(&panel, window, cx);\n        let weak_self = cx.entity().downgrade();\n        self.bottom_dock = Some(cx.new(|cx| {\n            let mut dock = Dock::bottom(weak_self.clone(), window, cx);\n            if let Some(size) = size {\n                dock.set_size(size, window, cx);\n            }\n            dock.set_panel(panel, window, cx);\n            dock.set_open(open, window, cx);\n            dock\n        }));\n        self.update_toggle_button_tab_panels(window, cx);\n    }\n\n    pub fn set_right_dock(\n        &mut self,\n        panel: DockItem,\n        size: Option<Pixels>,\n        open: bool,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.subscribe_item(&panel, window, cx);\n        let weak_self = cx.entity().downgrade();\n        self.right_dock = Some(cx.new(|cx| {\n            let mut dock = Dock::right(weak_self.clone(), window, cx);\n            if let Some(size) = size {\n                dock.set_size(size, window, cx);\n            }\n            dock.set_panel(panel, window, cx);\n            dock.set_open(open, window, cx);\n            dock\n        }));\n        self.update_toggle_button_tab_panels(window, cx);\n    }\n\n    /// Set locked state of the dock area, if locked, the dock area cannot be split or move, but allows to resize panels.\n    pub fn set_locked(&mut self, locked: bool, _window: &mut Window, _cx: &mut App) {\n        self.locked = locked;\n    }\n\n    /// Determine if the dock area is locked.\n    #[inline]\n    pub fn is_locked(&self) -> bool {\n        self.locked\n    }\n\n    /// Determine if the dock area has a dock at the given placement.\n    pub fn has_dock(&self, placement: DockPlacement) -> bool {\n        match placement {\n            DockPlacement::Left => self.left_dock.is_some(),\n            DockPlacement::Bottom => self.bottom_dock.is_some(),\n            DockPlacement::Right => self.right_dock.is_some(),\n            DockPlacement::Center => false,\n        }\n    }\n\n    /// Determine if the dock at the given placement is open.\n    pub fn is_dock_open(&self, placement: DockPlacement, cx: &App) -> bool {\n        match placement {\n            DockPlacement::Left => self\n                .left_dock\n                .as_ref()\n                .map(|dock| dock.read(cx).is_open())\n                .unwrap_or(false),\n            DockPlacement::Bottom => self\n                .bottom_dock\n                .as_ref()\n                .map(|dock| dock.read(cx).is_open())\n                .unwrap_or(false),\n            DockPlacement::Right => self\n                .right_dock\n                .as_ref()\n                .map(|dock| dock.read(cx).is_open())\n                .unwrap_or(false),\n            DockPlacement::Center => false,\n        }\n    }\n\n    /// Set the dock at the given placement to be open or closed.\n    ///\n    /// Only the left, bottom, right dock can be toggled.\n    pub fn set_dock_collapsible(\n        &mut self,\n        collapsible_edges: Edges<bool>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if let Some(left_dock) = self.left_dock.as_ref() {\n            left_dock.update(cx, |dock, cx| {\n                dock.set_collapsible(collapsible_edges.left, window, cx);\n            });\n        }\n\n        if let Some(bottom_dock) = self.bottom_dock.as_ref() {\n            bottom_dock.update(cx, |dock, cx| {\n                dock.set_collapsible(collapsible_edges.bottom, window, cx);\n            });\n        }\n\n        if let Some(right_dock) = self.right_dock.as_ref() {\n            right_dock.update(cx, |dock, cx| {\n                dock.set_collapsible(collapsible_edges.right, window, cx);\n            });\n        }\n    }\n\n    /// Determine if the dock at the given placement is collapsible.\n    pub fn is_dock_collapsible(&self, placement: DockPlacement, cx: &App) -> bool {\n        match placement {\n            DockPlacement::Left => self\n                .left_dock\n                .as_ref()\n                .map(|dock| dock.read(cx).collapsible)\n                .unwrap_or(false),\n            DockPlacement::Bottom => self\n                .bottom_dock\n                .as_ref()\n                .map(|dock| dock.read(cx).collapsible)\n                .unwrap_or(false),\n            DockPlacement::Right => self\n                .right_dock\n                .as_ref()\n                .map(|dock| dock.read(cx).collapsible)\n                .unwrap_or(false),\n            DockPlacement::Center => false,\n        }\n    }\n\n    /// Toggle the dock at the given placement.\n    pub fn toggle_dock(\n        &self,\n        placement: DockPlacement,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let dock = match placement {\n            DockPlacement::Left => &self.left_dock,\n            DockPlacement::Bottom => &self.bottom_dock,\n            DockPlacement::Right => &self.right_dock,\n            DockPlacement::Center => return,\n        };\n\n        if let Some(dock) = dock {\n            dock.update(cx, |view, cx| {\n                view.toggle_open(window, cx);\n            })\n        }\n    }\n\n    /// Set the visibility of the toggle button.\n    pub fn set_toggle_button_visible(&mut self, visible: bool, _: &mut Context<Self>) {\n        self.toggle_button_visible = visible;\n    }\n\n    /// Add a panel item to the dock area at the given placement.\n    pub fn add_panel(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        placement: DockPlacement,\n        bounds: Option<Bounds<Pixels>>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let weak_self = cx.entity().downgrade();\n        match placement {\n            DockPlacement::Left => {\n                if let Some(dock) = self.left_dock.as_ref() {\n                    dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))\n                } else {\n                    self.set_left_dock(\n                        DockItem::tabs(vec![panel], &weak_self, window, cx),\n                        None,\n                        true,\n                        window,\n                        cx,\n                    );\n                }\n            }\n            DockPlacement::Bottom => {\n                if let Some(dock) = self.bottom_dock.as_ref() {\n                    dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))\n                } else {\n                    self.set_bottom_dock(\n                        DockItem::tabs(vec![panel], &weak_self, window, cx),\n                        None,\n                        true,\n                        window,\n                        cx,\n                    );\n                }\n            }\n            DockPlacement::Right => {\n                if let Some(dock) = self.right_dock.as_ref() {\n                    dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))\n                } else {\n                    self.set_right_dock(\n                        DockItem::tabs(vec![panel], &weak_self, window, cx),\n                        None,\n                        true,\n                        window,\n                        cx,\n                    );\n                }\n            }\n            DockPlacement::Center => {\n                self.center\n                    .add_panel(panel, &cx.entity().downgrade(), bounds, window, cx);\n            }\n        }\n    }\n\n    /// Remove panel from the DockArea at the given placement.\n    pub fn remove_panel(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        placement: DockPlacement,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        match placement {\n            DockPlacement::Left => {\n                if let Some(dock) = self.left_dock.as_mut() {\n                    dock.update(cx, |dock, cx| {\n                        dock.remove_panel(panel, window, cx);\n                    });\n                }\n            }\n            DockPlacement::Right => {\n                if let Some(dock) = self.right_dock.as_mut() {\n                    dock.update(cx, |dock, cx| {\n                        dock.remove_panel(panel, window, cx);\n                    });\n                }\n            }\n            DockPlacement::Bottom => {\n                if let Some(dock) = self.bottom_dock.as_mut() {\n                    dock.update(cx, |dock, cx| {\n                        dock.remove_panel(panel, window, cx);\n                    });\n                }\n            }\n            DockPlacement::Center => {\n                self.center.remove_panel(panel, window, cx);\n            }\n        }\n        cx.notify();\n    }\n\n    /// Remove a panel from all docks.\n    pub fn remove_panel_from_all_docks(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.remove_panel(panel.clone(), DockPlacement::Center, window, cx);\n        self.remove_panel(panel.clone(), DockPlacement::Left, window, cx);\n        self.remove_panel(panel.clone(), DockPlacement::Right, window, cx);\n        self.remove_panel(panel.clone(), DockPlacement::Bottom, window, cx);\n    }\n\n    /// Load the state of the DockArea from the DockAreaState.\n    ///\n    /// See also [DockeArea::dump].\n    pub fn load(\n        &mut self,\n        state: DockAreaState,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Result<()> {\n        self.version = state.version;\n        let weak_self = cx.entity().downgrade();\n\n        if let Some(left_dock_state) = state.left_dock {\n            self.left_dock = Some(left_dock_state.to_dock(weak_self.clone(), window, cx));\n        }\n\n        if let Some(right_dock_state) = state.right_dock {\n            self.right_dock = Some(right_dock_state.to_dock(weak_self.clone(), window, cx));\n        }\n\n        if let Some(bottom_dock_state) = state.bottom_dock {\n            self.bottom_dock = Some(bottom_dock_state.to_dock(weak_self.clone(), window, cx));\n        }\n\n        self.center = state.center.to_item(weak_self, window, cx);\n        self.update_toggle_button_tab_panels(window, cx);\n        Ok(())\n    }\n\n    /// Dump the dock panels layout to PanelState.\n    ///\n    /// See also [DockArea::load].\n    pub fn dump(&self, cx: &App) -> DockAreaState {\n        let root = self.center.view();\n        let center = root.dump(cx);\n\n        let left_dock = self\n            .left_dock\n            .as_ref()\n            .map(|dock| DockState::new(dock.clone(), cx));\n        let right_dock = self\n            .right_dock\n            .as_ref()\n            .map(|dock| DockState::new(dock.clone(), cx));\n        let bottom_dock = self\n            .bottom_dock\n            .as_ref()\n            .map(|dock| DockState::new(dock.clone(), cx));\n\n        DockAreaState {\n            version: self.version,\n            center,\n            left_dock,\n            right_dock,\n            bottom_dock,\n        }\n    }\n\n    /// Subscribe event on the panels\n    #[allow(clippy::only_used_in_recursion)]\n    fn subscribe_item(&mut self, item: &DockItem, window: &mut Window, cx: &mut Context<Self>) {\n        match item {\n            DockItem::Split { items, view, .. } => {\n                for item in items {\n                    self.subscribe_item(item, window, cx);\n                }\n\n                self._subscriptions.push(cx.subscribe_in(\n                    view,\n                    window,\n                    move |_, _, event, window, cx| match event {\n                        PanelEvent::LayoutChanged => {\n                            cx.spawn_in(window, async move |view, window| {\n                                _ = view.update_in(window, |view, window, cx| {\n                                    view.update_toggle_button_tab_panels(window, cx)\n                                });\n                            })\n                            .detach();\n                            cx.emit(DockEvent::LayoutChanged);\n                        }\n                        _ => {}\n                    },\n                ));\n            }\n            DockItem::Tabs { .. } => {\n                // We subscribe to the tab panel event in StackPanel's insert_panel\n            }\n            DockItem::Tiles { .. } => {\n                // We subscribe to the tab panel event in Tiles's [`add_item`](Tiles::add_item)\n            }\n            DockItem::Panel { .. } => {\n                // Not supported\n            }\n        }\n    }\n\n    /// Subscribe zoom event on the panel\n    pub(crate) fn subscribe_panel<P: Panel>(\n        &mut self,\n        view: &Entity<P>,\n        window: &mut Window,\n        cx: &mut Context<DockArea>,\n    ) {\n        let subscription =\n            cx.subscribe_in(\n                view,\n                window,\n                move |_, panel, event, window, cx| match event {\n                    PanelEvent::ZoomIn => {\n                        let panel = panel.clone();\n                        cx.spawn_in(window, async move |view, window| {\n                            _ = view.update_in(window, |view, window, cx| {\n                                view.set_zoomed_in(panel, window, cx);\n                                cx.notify();\n                            });\n                        })\n                        .detach();\n                    }\n                    PanelEvent::ZoomOut => cx\n                        .spawn_in(window, async move |view, window| {\n                            _ = view.update_in(window, |view, window, cx| {\n                                view.set_zoomed_out(window, cx);\n                            });\n                        })\n                        .detach(),\n                    PanelEvent::LayoutChanged => {\n                        cx.spawn_in(window, async move |view, window| {\n                            _ = view.update_in(window, |view, window, cx| {\n                                view.update_toggle_button_tab_panels(window, cx)\n                            });\n                        })\n                        .detach();\n                        cx.emit(DockEvent::LayoutChanged);\n                    }\n                },\n            );\n\n        self._subscriptions.push(subscription);\n    }\n\n    /// Returns the ID of the dock area.\n    pub fn id(&self) -> SharedString {\n        self.id.clone()\n    }\n\n    pub fn set_zoomed_in<P: Panel>(\n        &mut self,\n        panel: Entity<P>,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.zoom_view = Some(panel.into());\n        cx.notify();\n    }\n\n    pub fn set_zoomed_out(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        self.zoom_view = None;\n        cx.notify();\n    }\n\n    fn render_items(&self, _window: &mut Window, _cx: &mut Context<Self>) -> AnyElement {\n        match &self.center {\n            DockItem::Split { view, .. } => view.clone().into_any_element(),\n            DockItem::Tabs { view, .. } => view.clone().into_any_element(),\n            DockItem::Tiles { view, .. } => view.clone().into_any_element(),\n            DockItem::Panel { view, .. } => view.clone().view().into_any_element(),\n        }\n    }\n\n    pub fn update_toggle_button_tab_panels(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        // Left toggle button\n        self.toggle_button_panels.left = self\n            .center\n            .left_top_tab_panel(cx)\n            .map(|view| view.entity_id());\n\n        // Right toggle button\n        self.toggle_button_panels.right = self\n            .center\n            .right_top_tab_panel(cx)\n            .map(|view| view.entity_id());\n\n        // Bottom toggle button\n        self.toggle_button_panels.bottom = self\n            .bottom_dock\n            .as_ref()\n            .and_then(|dock| dock.read(cx).panel.left_top_tab_panel(cx))\n            .map(|view| view.entity_id());\n    }\n}\nimpl EventEmitter<DockEvent> for DockArea {}\nimpl Render for DockArea {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let view = cx.entity().clone();\n\n        div()\n            .id(\"dock-area\")\n            .relative()\n            .size_full()\n            .overflow_hidden()\n            .on_prepaint(move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds))\n            .map(|this| {\n                if let Some(zoom_view) = self.zoom_view.clone() {\n                    this.child(zoom_view)\n                } else {\n                    match &self.center {\n                        DockItem::Tiles { view, .. } => {\n                            // render tiles\n                            this.child(view.clone())\n                        }\n                        _ => {\n                            // render dock\n                            this.child(\n                                div()\n                                    .flex()\n                                    .flex_row()\n                                    .h_full()\n                                    // Left dock\n                                    .when_some(self.left_dock.clone(), |this, dock| {\n                                        this.child(div().flex().flex_none().child(dock))\n                                    })\n                                    // Center\n                                    .child(\n                                        div()\n                                            .flex()\n                                            .flex_1()\n                                            .flex_col()\n                                            .overflow_hidden()\n                                            // Top center\n                                            .child(\n                                                div()\n                                                    .flex_1()\n                                                    .overflow_hidden()\n                                                    .child(self.render_items(window, cx)),\n                                            )\n                                            // Bottom Dock\n                                            .when_some(self.bottom_dock.clone(), |this, dock| {\n                                                this.child(dock)\n                                            }),\n                                    )\n                                    // Right Dock\n                                    .when_some(self.right_dock.clone(), |this, dock| {\n                                        this.child(div().flex().flex_none().child(dock))\n                                    }),\n                            )\n                        }\n                    }\n                }\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dock/panel.rs",
    "content": "use crate::{button::Button, dock::TabPanel, menu::PopupMenu};\nuse gpui::{\n    AnyElement, AnyView, App, AppContext as _, Context, Entity, EntityId, EventEmitter,\n    FocusHandle, Focusable, Global, Hsla, IntoElement, Render, SharedString, WeakEntity, Window,\n};\nuse rust_i18n::t;\nuse std::{collections::HashMap, sync::Arc};\n\nuse super::{DockArea, PanelInfo, PanelState, invalid_panel::InvalidPanel};\n\npub enum PanelEvent {\n    ZoomIn,\n    ZoomOut,\n    LayoutChanged,\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\npub enum PanelStyle {\n    /// Display the TabBar when there are multiple tabs, otherwise display the simple title.\n    #[default]\n    Auto,\n    /// Always display the tab bar.\n    TabBar,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct TitleStyle {\n    pub background: Hsla,\n    pub foreground: Hsla,\n}\n\n#[derive(Clone, Copy, Default)]\npub enum PanelControl {\n    Both,\n    #[default]\n    Menu,\n    Toolbar,\n}\n\nimpl PanelControl {\n    #[inline]\n    pub fn toolbar_visible(&self) -> bool {\n        matches!(self, PanelControl::Both | PanelControl::Toolbar)\n    }\n\n    #[inline]\n    pub fn menu_visible(&self) -> bool {\n        matches!(self, PanelControl::Both | PanelControl::Menu)\n    }\n}\n\n/// The Panel trait used to define the panel.\n#[allow(unused_variables)]\npub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {\n    /// The name of the panel used to serialize, deserialize and identify the panel.\n    ///\n    /// This is used to identify the panel when deserializing the panel.\n    /// Once you have defined a panel name, this must not be changed.\n    fn panel_name(&self) -> &'static str;\n\n    /// The name of the tab of the panel, default is `None`.\n    ///\n    /// Used to display in the already collapsed tab panel.\n    fn tab_name(&self, cx: &App) -> Option<SharedString> {\n        None\n    }\n\n    /// The title of the panel\n    fn title(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        t!(\"Dock.Unnamed\")\n    }\n\n    /// The theme of the panel title, default is `None`.\n    fn title_style(&self, cx: &App) -> Option<TitleStyle> {\n        None\n    }\n\n    /// The suffix of the panel title, default is `None`.\n    ///\n    /// This is used to add a suffix element to the panel title.\n    fn title_suffix(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Option<impl IntoElement> {\n        None::<gpui::Div>\n    }\n\n    /// Whether the panel can be closed, default is `true`.\n    ///\n    /// This method called in Panel render, we should make sure it is fast.\n    fn closable(&self, cx: &App) -> bool {\n        true\n    }\n\n    /// Return `PanelControl` if the panel is zoomable, default is `PanelControl::Menu`.\n    ///\n    /// This method called in Panel render, we should make sure it is fast.\n    fn zoomable(&self, cx: &App) -> Option<PanelControl> {\n        Some(PanelControl::Menu)\n    }\n\n    /// Return false to hide panel, true to show panel, default is `true`.\n    ///\n    /// This method called in Panel render, we should make sure it is fast.\n    fn visible(&self, cx: &App) -> bool {\n        true\n    }\n\n    /// Set active state of the panel.\n    ///\n    /// This method will be called when the panel is active or inactive.\n    ///\n    /// The last_active_panel and current_active_panel will be touched when the panel is active.\n    fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {}\n\n    /// Set zoomed state of the panel.\n    ///\n    /// This method will be called when the panel is zoomed or unzoomed.\n    ///\n    /// Only current Panel will touch this method.\n    fn set_zoomed(&mut self, zoomed: bool, window: &mut Window, cx: &mut Context<Self>) {}\n\n    /// When this Panel is added to a TabPanel, this will be called.\n    fn on_added_to(\n        &mut self,\n        tab_panel: WeakEntity<TabPanel>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n    }\n\n    /// When this Panel is removed from a TabPanel, this will be called.\n    fn on_removed(&mut self, window: &mut Window, cx: &mut Context<Self>) {}\n\n    /// The addition dropdown menu of the panel, default is `None`.\n    fn dropdown_menu(\n        &mut self,\n        this: PopupMenu,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> PopupMenu {\n        this\n    }\n\n    /// The addition toolbar buttons of the panel used to show in the right of the title bar, default is `None`.\n    fn toolbar_buttons(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Option<Vec<Button>> {\n        None\n    }\n\n    /// Dump the panel, used to serialize the panel.\n    fn dump(&self, cx: &App) -> PanelState {\n        PanelState::new(self)\n    }\n\n    /// Whether the panel has inner padding when the panel is in the tabs layout, default is `true`.\n    fn inner_padding(&self, cx: &App) -> bool {\n        true\n    }\n}\n\n/// The PanelView trait used to define the panel view.\n#[allow(unused_variables)]\npub trait PanelView: 'static + Send + Sync {\n    fn panel_name(&self, cx: &App) -> &'static str;\n    fn panel_id(&self, cx: &App) -> EntityId;\n    fn tab_name(&self, cx: &App) -> Option<SharedString>;\n    fn title(&self, window: &mut Window, cx: &mut App) -> AnyElement;\n    fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement>;\n    fn title_style(&self, cx: &App) -> Option<TitleStyle>;\n    fn closable(&self, cx: &App) -> bool;\n    fn zoomable(&self, cx: &App) -> Option<PanelControl>;\n    fn visible(&self, cx: &App) -> bool;\n    fn set_active(&self, active: bool, window: &mut Window, cx: &mut App);\n    fn set_zoomed(&self, zoomed: bool, window: &mut Window, cx: &mut App);\n    fn on_added_to(&self, tab_panel: WeakEntity<TabPanel>, window: &mut Window, cx: &mut App);\n    fn on_removed(&self, window: &mut Window, cx: &mut App);\n    fn dropdown_menu(&self, menu: PopupMenu, window: &mut Window, cx: &mut App) -> PopupMenu;\n    fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>>;\n    fn view(&self) -> AnyView;\n    fn focus_handle(&self, cx: &App) -> FocusHandle;\n    fn dump(&self, cx: &App) -> PanelState;\n    fn inner_padding(&self, cx: &App) -> bool;\n}\n\nimpl<T: Panel> PanelView for Entity<T> {\n    fn panel_name(&self, cx: &App) -> &'static str {\n        self.read(cx).panel_name()\n    }\n\n    fn panel_id(&self, _: &App) -> EntityId {\n        self.entity_id()\n    }\n\n    fn tab_name(&self, cx: &App) -> Option<SharedString> {\n        self.read(cx).tab_name(cx)\n    }\n\n    fn title(&self, window: &mut Window, cx: &mut App) -> AnyElement {\n        self.update(cx, |this, cx| this.title(window, cx).into_any_element())\n    }\n\n    fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement> {\n        self.update(cx, |this, cx| {\n            this.title_suffix(window, cx)\n                .map(|el| el.into_any_element())\n        })\n    }\n\n    fn title_style(&self, cx: &App) -> Option<TitleStyle> {\n        self.read(cx).title_style(cx)\n    }\n\n    fn closable(&self, cx: &App) -> bool {\n        self.read(cx).closable(cx)\n    }\n\n    fn zoomable(&self, cx: &App) -> Option<PanelControl> {\n        self.read(cx).zoomable(cx)\n    }\n\n    fn visible(&self, cx: &App) -> bool {\n        self.read(cx).visible(cx)\n    }\n\n    fn set_active(&self, active: bool, window: &mut Window, cx: &mut App) {\n        self.update(cx, |this, cx| {\n            this.set_active(active, window, cx);\n        })\n    }\n\n    fn set_zoomed(&self, zoomed: bool, window: &mut Window, cx: &mut App) {\n        self.update(cx, |this, cx| {\n            this.set_zoomed(zoomed, window, cx);\n        })\n    }\n\n    fn on_added_to(&self, tab_panel: WeakEntity<TabPanel>, window: &mut Window, cx: &mut App) {\n        self.update(cx, |this, cx| this.on_added_to(tab_panel, window, cx));\n    }\n\n    fn on_removed(&self, window: &mut Window, cx: &mut App) {\n        self.update(cx, |this, cx| this.on_removed(window, cx));\n    }\n\n    fn dropdown_menu(&self, menu: PopupMenu, window: &mut Window, cx: &mut App) -> PopupMenu {\n        self.update(cx, |this, cx| this.dropdown_menu(menu, window, cx))\n    }\n\n    fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>> {\n        self.update(cx, |this, cx| this.toolbar_buttons(window, cx))\n    }\n\n    fn view(&self) -> AnyView {\n        self.clone().into()\n    }\n\n    fn focus_handle(&self, cx: &App) -> FocusHandle {\n        self.read(cx).focus_handle(cx)\n    }\n\n    fn dump(&self, cx: &App) -> PanelState {\n        self.read(cx).dump(cx)\n    }\n\n    fn inner_padding(&self, cx: &App) -> bool {\n        self.read(cx).inner_padding(cx)\n    }\n}\n\nimpl From<&dyn PanelView> for AnyView {\n    fn from(handle: &dyn PanelView) -> Self {\n        handle.view()\n    }\n}\n\nimpl<T: Panel> From<&dyn PanelView> for Entity<T> {\n    fn from(value: &dyn PanelView) -> Self {\n        value.view().downcast::<T>().unwrap()\n    }\n}\n\nimpl PartialEq for dyn PanelView {\n    fn eq(&self, other: &Self) -> bool {\n        self.view() == other.view()\n    }\n}\n\npub struct PanelRegistry {\n    pub(super) items: HashMap<\n        String,\n        Arc<\n            dyn Fn(\n                WeakEntity<DockArea>,\n                &PanelState,\n                &PanelInfo,\n                &mut Window,\n                &mut App,\n            ) -> Box<dyn PanelView>,\n        >,\n    >,\n}\nimpl PanelRegistry {\n    /// Initialize the panel registry.\n    pub(crate) fn init(cx: &mut App) {\n        if let None = cx.try_global::<PanelRegistry>() {\n            cx.set_global(PanelRegistry::new());\n        }\n    }\n\n    pub fn new() -> Self {\n        Self {\n            items: HashMap::new(),\n        }\n    }\n\n    pub fn global(cx: &App) -> &Self {\n        cx.global::<PanelRegistry>()\n    }\n\n    pub fn global_mut(cx: &mut App) -> &mut Self {\n        cx.global_mut::<PanelRegistry>()\n    }\n\n    /// Build a panel by name.\n    ///\n    /// If not registered, return InvalidPanel.\n    pub fn build_panel(\n        panel_name: &str,\n        dock_area: WeakEntity<DockArea>,\n        panel_state: &PanelState,\n        panel_info: &PanelInfo,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Box<dyn PanelView> {\n        if let Some(view) = Self::global(cx)\n            .items\n            .get(panel_name)\n            .cloned()\n            .map(|f| f(dock_area, panel_state, panel_info, window, cx))\n        {\n            return view;\n        } else {\n            // Show an invalid panel if the panel is not registered.\n            Box::new(cx.new(|cx| InvalidPanel::new(&panel_name, panel_state.clone(), window, cx)))\n        }\n    }\n}\nimpl Global for PanelRegistry {}\n\n/// Register the Panel init by panel_name to global registry.\npub fn register_panel<F>(cx: &mut App, panel_name: &str, deserialize: F)\nwhere\n    F: Fn(\n            WeakEntity<DockArea>,\n            &PanelState,\n            &PanelInfo,\n            &mut Window,\n            &mut App,\n        ) -> Box<dyn PanelView>\n        + 'static,\n{\n    PanelRegistry::init(cx);\n    PanelRegistry::global_mut(cx)\n        .items\n        .insert(panel_name.to_string(), Arc::new(deserialize));\n}\n"
  },
  {
    "path": "crates/ui/src/dock/stack_panel.rs",
    "content": "use std::sync::Arc;\n\nuse crate::{\n    ActiveTheme, AxisExt as _, Placement,\n    dock::PanelInfo,\n    h_flex,\n    resizable::{\n        PANEL_MIN_SIZE, ResizablePanelEvent, ResizablePanelGroup, ResizablePanelState,\n        ResizableState, resizable_panel,\n    },\n};\n\nuse super::{DockArea, Panel, PanelEvent, PanelState, PanelView, TabPanel};\nuse gpui::{\n    App, AppContext as _, Axis, Context, DismissEvent, Entity, EventEmitter, FocusHandle,\n    Focusable, IntoElement, ParentElement, Pixels, Render, Styled, Subscription, WeakEntity,\n    Window,\n};\nuse smallvec::SmallVec;\n\npub struct StackPanel {\n    pub(super) parent: Option<WeakEntity<StackPanel>>,\n    pub(super) axis: Axis,\n    focus_handle: FocusHandle,\n    pub(crate) panels: SmallVec<[Arc<dyn PanelView>; 2]>,\n    state: Entity<ResizableState>,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl Panel for StackPanel {\n    fn panel_name(&self) -> &'static str {\n        \"StackPanel\"\n    }\n\n    fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {\n        \"StackPanel\"\n    }\n\n    fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {\n        for panel in &self.panels {\n            panel.set_active(active, window, cx);\n        }\n    }\n    fn dump(&self, cx: &App) -> PanelState {\n        let sizes = self.state.read(cx).sizes().clone();\n        let mut state = PanelState::new(self);\n        for panel in &self.panels {\n            state.add_child(panel.dump(cx));\n            state.info = PanelInfo::stack(sizes.clone(), self.axis);\n        }\n\n        state\n    }\n}\n\nimpl StackPanel {\n    pub fn new(axis: Axis, _: &mut Window, cx: &mut Context<Self>) -> Self {\n        let state = cx.new(|_| ResizableState::default());\n\n        let _subscriptions = vec![\n            // Bubble up the resize event.\n            cx.subscribe(&state, |_, _, _: &ResizablePanelEvent, cx| {\n                cx.emit(PanelEvent::LayoutChanged)\n            }),\n        ];\n\n        Self {\n            axis,\n            parent: None,\n            focus_handle: cx.focus_handle(),\n            panels: SmallVec::new(),\n            state,\n            _subscriptions,\n        }\n    }\n\n    /// The first level of the stack panel is root, will not have a parent.\n    fn is_root(&self) -> bool {\n        self.parent.is_none()\n    }\n\n    /// Return true if self or parent only have last panel.\n    pub(super) fn is_last_panel(&self, cx: &App) -> bool {\n        if self.panels.len() > 1 {\n            return false;\n        }\n\n        if let Some(parent) = &self.parent {\n            if let Some(parent) = parent.upgrade() {\n                return parent.read(cx).is_last_panel(cx);\n            }\n        }\n\n        true\n    }\n\n    pub(super) fn panels_len(&self) -> usize {\n        self.panels.len()\n    }\n\n    /// Return the index of the panel.\n    pub(crate) fn index_of_panel(&self, panel: Arc<dyn PanelView>) -> Option<usize> {\n        self.panels.iter().position(|p| p == &panel)\n    }\n\n    fn assert_panel_is_valid(&self, panel: &Arc<dyn PanelView>) {\n        assert!(\n            panel.view().downcast::<TabPanel>().is_ok()\n                || panel.view().downcast::<StackPanel>().is_ok(),\n            \"Panel must be a `TabPanel` or `StackPanel`\"\n        );\n    }\n\n    /// Add a panel at the end of the stack.\n    ///\n    /// If `size` is `None`, the panel will be given the average size of all panels in the stack.\n    ///\n    /// The `panel` must be a [`TabPanel`] or [`StackPanel`].\n    pub fn add_panel(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        size: Option<Pixels>,\n        dock_area: WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.insert_panel(panel, self.panels.len(), size, dock_area, window, cx);\n    }\n\n    /// Add a panel at the [`Placement`].\n    ///\n    /// The `panel` must be a [`TabPanel`] or [`StackPanel`].\n    pub fn add_panel_at(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        placement: Placement,\n        size: Option<Pixels>,\n        dock_area: WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.insert_panel_at(\n            panel,\n            self.panels_len(),\n            placement,\n            size,\n            dock_area,\n            window,\n            cx,\n        );\n    }\n\n    /// Insert a panel at the index.\n    ///\n    /// The `panel` must be a [`TabPanel`] or [`StackPanel`].\n    #[allow(clippy::too_many_arguments)]\n    pub fn insert_panel_at(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        ix: usize,\n        placement: Placement,\n        size: Option<Pixels>,\n        dock_area: WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        match placement {\n            Placement::Top | Placement::Left => {\n                self.insert_panel_before(panel, ix, size, dock_area, window, cx)\n            }\n            Placement::Right | Placement::Bottom => {\n                self.insert_panel_after(panel, ix, size, dock_area, window, cx)\n            }\n        }\n    }\n\n    /// Insert a panel at the index.\n    ///\n    /// The `panel` must be a [`TabPanel`] or [`StackPanel`].\n    pub fn insert_panel_before(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        ix: usize,\n        size: Option<Pixels>,\n        dock_area: WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.insert_panel(panel, ix, size, dock_area, window, cx);\n    }\n\n    /// Insert a panel after the index.\n    ///\n    /// The `panel` must be a [`TabPanel`] or [`StackPanel`].\n    pub fn insert_panel_after(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        ix: usize,\n        size: Option<Pixels>,\n        dock_area: WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.insert_panel(panel, ix + 1, size, dock_area, window, cx);\n    }\n\n    fn insert_panel(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        ix: usize,\n        size: Option<Pixels>,\n        dock_area: WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.assert_panel_is_valid(&panel);\n\n        // If the panel is already in the stack, return.\n        if let Some(_) = self.index_of_panel(panel.clone()) {\n            return;\n        }\n\n        let view = cx.entity().clone();\n        window.defer(cx, {\n            let panel = panel.clone();\n\n            move |window, cx| {\n                // If the panel is a TabPanel, set its parent to this.\n                if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {\n                    tab_panel.update(cx, |tab_panel, _| tab_panel.set_parent(view.downgrade()));\n                } else if let Ok(stack_panel) = panel.view().downcast::<Self>() {\n                    stack_panel.update(cx, |stack_panel, _| {\n                        stack_panel.parent = Some(view.downgrade())\n                    });\n                }\n\n                // Subscribe to the panel's layout change event.\n                _ = dock_area.update(cx, |this, cx| {\n                    if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {\n                        this.subscribe_panel(&tab_panel, window, cx);\n                    } else if let Ok(stack_panel) = panel.view().downcast::<Self>() {\n                        this.subscribe_panel(&stack_panel, window, cx);\n                    }\n                });\n            }\n        });\n\n        let ix = if ix > self.panels.len() {\n            self.panels.len()\n        } else {\n            ix\n        };\n\n        // Get avg size of all panels to insert new panel, if size is None.\n        let size = match size {\n            Some(size) => size,\n            None => {\n                let state = self.state.read(cx);\n                (state.container_size() / (state.sizes().len() + 1) as f32).max(PANEL_MIN_SIZE)\n            }\n        };\n\n        self.panels.insert(ix, panel.clone());\n        self.state.update(cx, |state, cx| {\n            state.insert_panel(Some(size), Some(ix), cx);\n        });\n        cx.emit(PanelEvent::LayoutChanged);\n        cx.notify();\n    }\n\n    /// Remove panel from the stack.\n    ///\n    /// If `ix` is not found, do nothing.\n    pub fn remove_panel(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let Some(ix) = self.index_of_panel(panel.clone()) else {\n            return;\n        };\n\n        self.panels.remove(ix);\n        self.state.update(cx, |state, cx| {\n            state.remove_panel(ix, cx);\n        });\n\n        cx.emit(PanelEvent::LayoutChanged);\n        self.remove_self_if_empty(window, cx);\n    }\n\n    /// Replace the old panel with the new panel at same index.\n    pub(super) fn replace_panel(\n        &mut self,\n        old_panel: Arc<dyn PanelView>,\n        new_panel: Entity<StackPanel>,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if let Some(ix) = self.index_of_panel(old_panel.clone()) {\n            self.panels[ix] = Arc::new(new_panel.clone());\n\n            let panel_state = ResizablePanelState::default();\n            self.state.update(cx, |state, cx| {\n                state.replace_panel(ix, panel_state, cx);\n            });\n            cx.emit(PanelEvent::LayoutChanged);\n        }\n    }\n\n    /// If children is empty, remove self from parent view.\n    pub(crate) fn remove_self_if_empty(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        if self.is_root() {\n            return;\n        }\n\n        if !self.panels.is_empty() {\n            return;\n        }\n\n        let view = cx.entity().clone();\n        if let Some(parent) = self.parent.as_ref() {\n            _ = parent.update(cx, |parent, cx| {\n                parent.remove_panel(Arc::new(view.clone()), window, cx);\n            });\n        }\n\n        cx.emit(PanelEvent::LayoutChanged);\n        cx.notify();\n    }\n\n    /// Find the first top left in the stack.\n    pub(super) fn left_top_tab_panel(\n        &self,\n        check_parent: bool,\n        cx: &App,\n    ) -> Option<Entity<TabPanel>> {\n        if check_parent {\n            if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {\n                if let Some(panel) = parent.read(cx).left_top_tab_panel(true, cx) {\n                    return Some(panel);\n                }\n            }\n        }\n\n        let first_panel = self.panels.first();\n        if let Some(view) = first_panel {\n            if let Ok(tab_panel) = view.view().downcast::<TabPanel>() {\n                Some(tab_panel)\n            } else if let Ok(stack_panel) = view.view().downcast::<StackPanel>() {\n                stack_panel.read(cx).left_top_tab_panel(false, cx)\n            } else {\n                None\n            }\n        } else {\n            None\n        }\n    }\n\n    /// Find the first top right in the stack.\n    pub(super) fn right_top_tab_panel(\n        &self,\n        check_parent: bool,\n        cx: &App,\n    ) -> Option<Entity<TabPanel>> {\n        if check_parent {\n            if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {\n                if let Some(panel) = parent.read(cx).right_top_tab_panel(true, cx) {\n                    return Some(panel);\n                }\n            }\n        }\n\n        let panel = if self.axis.is_vertical() {\n            self.panels.first()\n        } else {\n            self.panels.last()\n        };\n\n        if let Some(view) = panel {\n            if let Ok(tab_panel) = view.view().downcast::<TabPanel>() {\n                Some(tab_panel)\n            } else if let Ok(stack_panel) = view.view().downcast::<StackPanel>() {\n                stack_panel.read(cx).right_top_tab_panel(false, cx)\n            } else {\n                None\n            }\n        } else {\n            None\n        }\n    }\n\n    /// Remove all panels from the stack.\n    pub(super) fn remove_all_panels(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        self.panels.clear();\n        self.state.update(cx, |state, cx| {\n            state.clear();\n            cx.notify();\n        });\n    }\n\n    /// Change the axis of the stack panel.\n    pub(super) fn set_axis(&mut self, axis: Axis, _: &mut Window, cx: &mut Context<Self>) {\n        self.axis = axis;\n        cx.notify();\n    }\n}\n\nimpl Focusable for StackPanel {\n    fn focus_handle(&self, _cx: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\nimpl EventEmitter<PanelEvent> for StackPanel {}\nimpl EventEmitter<DismissEvent> for StackPanel {}\nimpl Render for StackPanel {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        h_flex()\n            .size_full()\n            .overflow_hidden()\n            .bg(cx.theme().tab_bar)\n            .child(\n                ResizablePanelGroup::new(\"stack-panel-group\")\n                    .with_state(&self.state)\n                    .axis(self.axis)\n                    .children(self.panels.clone().into_iter().map(|panel| {\n                        resizable_panel()\n                            .child(panel.view())\n                            .visible(panel.visible(cx))\n                    })),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dock/state.rs",
    "content": "use gpui::{App, AppContext, Axis, Bounds, Entity, Pixels, WeakEntity, Window, point, px, size};\nuse itertools::Itertools as _;\nuse serde::{Deserialize, Serialize};\n\nuse super::{Dock, DockArea, DockItem, DockPlacement, Panel, PanelRegistry};\n\n/// Used to serialize and deserialize the DockArea\n#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]\npub struct DockAreaState {\n    /// The version is used to mark this persisted state is compatible with the current version\n    /// For example, some times we many totally changed the structure of the Panel,\n    /// then we can compare the version to decide whether we can use the state or ignore.\n    #[serde(default)]\n    pub version: Option<usize>,\n    pub center: PanelState,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub left_dock: Option<DockState>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub right_dock: Option<DockState>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub bottom_dock: Option<DockState>,\n}\n\n/// Used to serialize and deserialize the Dock\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\npub struct DockState {\n    panel: PanelState,\n    placement: DockPlacement,\n    size: Pixels,\n    open: bool,\n}\n\nimpl DockState {\n    pub fn new(dock: Entity<Dock>, cx: &App) -> Self {\n        let dock = dock.read(cx);\n\n        Self {\n            placement: dock.placement,\n            size: dock.size,\n            open: dock.open,\n            panel: dock.panel.view().dump(cx),\n        }\n    }\n\n    /// Convert the DockState to Dock\n    pub fn to_dock(\n        &self,\n        dock_area: WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Entity<Dock> {\n        let item = self.panel.to_item(dock_area.clone(), window, cx);\n        cx.new(|cx| {\n            Dock::from_state(\n                dock_area.clone(),\n                self.placement,\n                self.size,\n                item,\n                self.open,\n                window,\n                cx,\n            )\n        })\n    }\n}\n\n/// Used to serialize and deserialize the DockerItem\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\npub struct PanelState {\n    pub panel_name: String,\n    pub children: Vec<PanelState>,\n    pub info: PanelInfo,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]\npub struct TileMeta {\n    pub bounds: Bounds<Pixels>,\n    pub z_index: usize,\n}\n\nimpl Default for TileMeta {\n    fn default() -> Self {\n        Self {\n            bounds: Bounds { origin: point(px(10.), px(10.)), size: size(px(200.), px(200.)) },\n            z_index: 0,\n        }\n    }\n}\n\nimpl From<Bounds<Pixels>> for TileMeta {\n    fn from(bounds: Bounds<Pixels>) -> Self {\n        Self { bounds, z_index: 0 }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\npub enum PanelInfo {\n    #[serde(rename = \"stack\")]\n    Stack {\n        sizes: Vec<Pixels>,\n        axis: usize, // 0 for horizontal, 1 for vertical\n    },\n    #[serde(rename = \"tabs\")]\n    Tabs { active_index: usize },\n    #[serde(rename = \"panel\")]\n    Panel(serde_json::Value),\n    #[serde(rename = \"tiles\")]\n    Tiles { metas: Vec<TileMeta> },\n}\n\nimpl PanelInfo {\n    pub fn stack(sizes: Vec<Pixels>, axis: Axis) -> Self {\n        Self::Stack { sizes, axis: if axis == Axis::Horizontal { 0 } else { 1 } }\n    }\n\n    pub fn tabs(active_index: usize) -> Self {\n        Self::Tabs { active_index }\n    }\n\n    pub fn panel(info: serde_json::Value) -> Self {\n        Self::Panel(info)\n    }\n\n    pub fn tiles(metas: Vec<TileMeta>) -> Self {\n        Self::Tiles { metas }\n    }\n\n    pub fn axis(&self) -> Option<Axis> {\n        match self {\n            Self::Stack { axis, .. } => {\n                Some(if *axis == 0 { Axis::Horizontal } else { Axis::Vertical })\n            }\n            _ => None,\n        }\n    }\n\n    pub fn sizes(&self) -> Option<&Vec<Pixels>> {\n        match self {\n            Self::Stack { sizes, .. } => Some(sizes),\n            _ => None,\n        }\n    }\n\n    pub fn active_index(&self) -> Option<usize> {\n        match self {\n            Self::Tabs { active_index } => Some(*active_index),\n            _ => None,\n        }\n    }\n}\n\nimpl Default for PanelState {\n    fn default() -> Self {\n        Self {\n            panel_name: \"\".to_string(),\n            children: Vec::new(),\n            info: PanelInfo::Panel(serde_json::Value::Null),\n        }\n    }\n}\n\nimpl PanelState {\n    pub fn new<P: Panel>(panel: &P) -> Self {\n        Self { panel_name: panel.panel_name().to_string(), ..Default::default() }\n    }\n\n    pub fn add_child(&mut self, panel: PanelState) {\n        self.children.push(panel);\n    }\n\n    pub fn to_item(\n        &self,\n        dock_area: WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> DockItem {\n        let info = self.info.clone();\n\n        let items: Vec<DockItem> = self\n            .children\n            .iter()\n            .map(|child| child.to_item(dock_area.clone(), window, cx))\n            .collect();\n\n        match info {\n            PanelInfo::Stack { sizes, axis } => {\n                let axis = if axis == 0 { Axis::Horizontal } else { Axis::Vertical };\n                let sizes = sizes.iter().map(|s| Some(*s)).collect_vec();\n                DockItem::split_with_sizes(axis, items, sizes, &dock_area, window, cx)\n            }\n            PanelInfo::Tabs { active_index } => {\n                if items.len() == 1 {\n                    return items[0].clone();\n                }\n\n                let items = items\n                    .iter()\n                    .flat_map(|item| match item {\n                        DockItem::Tabs { items, .. } => items.clone(),\n                        _ => {\n                            // ignore invalid panels in tabs\n                            vec![]\n                        }\n                    })\n                    .collect_vec();\n\n                DockItem::tabs(items, &dock_area, window, cx).active_index(active_index, cx)\n            }\n            PanelInfo::Panel(_) => {\n                let view = PanelRegistry::build_panel(\n                    &self.panel_name,\n                    dock_area.clone(),\n                    self,\n                    &info,\n                    window,\n                    cx,\n                );\n                DockItem::tabs(vec![view.into()], &dock_area, window, cx)\n            }\n            PanelInfo::Tiles { metas } => DockItem::tiles(items, metas, &dock_area, window, cx),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use gpui::px;\n\n    use super::*;\n    #[test]\n    fn test_deserialize_item_state() {\n        let json = include_str!(\"../fixtures/layout.json\");\n        let state: DockAreaState = serde_json::from_str(json).unwrap();\n        assert_eq!(state.version, None);\n        assert_eq!(state.center.panel_name, \"StackPanel\");\n        assert_eq!(state.center.children.len(), 2);\n        assert_eq!(state.center.children[0].panel_name, \"TabPanel\");\n        assert_eq!(state.center.children[1].children.len(), 1);\n        assert_eq!(state.center.children[1].children[0].panel_name, \"StoryContainer\");\n        assert_eq!(state.center.children[1].panel_name, \"TabPanel\");\n\n        let left_dock = state.left_dock.unwrap();\n        assert_eq!(left_dock.open, true);\n        assert_eq!(left_dock.size, px(350.0));\n        assert_eq!(left_dock.placement, DockPlacement::Left);\n        assert_eq!(left_dock.panel.panel_name, \"TabPanel\");\n        assert_eq!(left_dock.panel.children.len(), 1);\n        assert_eq!(left_dock.panel.children[0].panel_name, \"StoryContainer\");\n\n        let bottom_dock = state.bottom_dock.unwrap();\n        assert_eq!(bottom_dock.open, true);\n        assert_eq!(bottom_dock.size, px(200.0));\n        assert_eq!(bottom_dock.panel.panel_name, \"TabPanel\");\n        assert_eq!(bottom_dock.panel.children.len(), 2);\n        assert_eq!(bottom_dock.panel.children[0].panel_name, \"StoryContainer\");\n\n        let right_dock = state.right_dock.unwrap();\n        assert_eq!(right_dock.open, true);\n        assert_eq!(right_dock.size, px(320.0));\n        assert_eq!(right_dock.panel.panel_name, \"TabPanel\");\n        assert_eq!(right_dock.panel.children.len(), 1);\n        assert_eq!(right_dock.panel.children[0].panel_name, \"StoryContainer\");\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dock/tab_panel.rs",
    "content": "use std::sync::Arc;\n\nuse gpui::{\n    App, AppContext, Context, Corner, DismissEvent, Div, DragMoveEvent, Empty, Entity,\n    EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, ParentElement,\n    Pixels, Render, ScrollHandle, SharedString, StatefulInteractiveElement, StyleRefinement,\n    Styled, WeakEntity, Window, div, prelude::FluentBuilder, px, relative, rems,\n};\nuse rust_i18n::t;\n\nuse crate::{\n    ActiveTheme, AxisExt, IconName, Placement, Selectable, Sizable,\n    button::{Button, ButtonVariants as _},\n    dock::PanelInfo,\n    h_flex,\n    menu::{DropdownMenu, PopupMenu},\n    tab::{Tab, TabBar},\n    v_flex,\n};\n\nuse super::{\n    ClosePanel, DockArea, DockPlacement, Panel, PanelControl, PanelEvent, PanelState, PanelStyle,\n    PanelView, StackPanel, ToggleZoom,\n};\n\n#[derive(Clone)]\nstruct TabState {\n    closable: bool,\n    zoomable: Option<PanelControl>,\n    draggable: bool,\n    droppable: bool,\n    active_panel: Option<Arc<dyn PanelView>>,\n}\n\n#[derive(Clone)]\npub(crate) struct DragPanel {\n    pub(crate) panel: Arc<dyn PanelView>,\n    pub(crate) tab_panel: Entity<TabPanel>,\n}\n\nimpl DragPanel {\n    pub(crate) fn new(panel: Arc<dyn PanelView>, tab_panel: Entity<TabPanel>) -> Self {\n        Self { panel, tab_panel }\n    }\n}\n\nimpl Render for DragPanel {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .id(\"drag-panel\")\n            .cursor_grab()\n            .py_1()\n            .px_3()\n            .w_24()\n            .overflow_hidden()\n            .whitespace_nowrap()\n            .border_1()\n            .border_color(cx.theme().border)\n            .rounded(cx.theme().radius)\n            .text_color(cx.theme().tab_foreground)\n            .bg(cx.theme().tab_active)\n            .opacity(0.75)\n            .child(self.panel.title(window, cx))\n    }\n}\n\npub struct TabPanel {\n    focus_handle: FocusHandle,\n    dock_area: WeakEntity<DockArea>,\n    /// The stock_panel can be None, if is None, that means the panels can't be split or move\n    stack_panel: Option<WeakEntity<StackPanel>>,\n    pub(crate) panels: Vec<Arc<dyn PanelView>>,\n    pub(crate) active_ix: usize,\n    /// If this is true, the Panel closable will follow the active panel's closable,\n    /// otherwise this TabPanel will not able to close\n    ///\n    /// This is used for Dock to limit the last TabPanel not able to close, see [`super::Dock::new`].\n    pub(crate) closable: bool,\n\n    tab_bar_scroll_handle: ScrollHandle,\n    zoomed: bool,\n    collapsed: bool,\n    /// When drag move, will get the placement of the panel to be split\n    will_split_placement: Option<Placement>,\n    /// Is TabPanel used in Tiles.\n    in_tiles: bool,\n}\n\nimpl Panel for TabPanel {\n    fn panel_name(&self) -> &'static str {\n        \"TabPanel\"\n    }\n\n    fn title(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        self.active_panel(cx)\n            .map(|panel| panel.title(window, cx))\n            .unwrap_or(\"Empty Tab\".into_any_element())\n    }\n\n    fn closable(&self, cx: &App) -> bool {\n        if !self.closable {\n            return false;\n        }\n\n        // 1. When is the final panel in the dock, it will not able to close.\n        // 2. When is in the Tiles, it will always able to close (by active panel state).\n        if !self.draggable(cx) && !self.in_tiles {\n            return false;\n        }\n\n        self.active_panel(cx)\n            .map(|panel| panel.closable(cx))\n            .unwrap_or(false)\n    }\n\n    fn zoomable(&self, cx: &App) -> Option<PanelControl> {\n        self.active_panel(cx).and_then(|panel| panel.zoomable(cx))\n    }\n\n    fn visible(&self, cx: &App) -> bool {\n        self.visible_panels(cx).next().is_some()\n    }\n\n    fn dropdown_menu(\n        &mut self,\n        menu: PopupMenu,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> PopupMenu {\n        if let Some(panel) = self.active_panel(cx) {\n            panel.dropdown_menu(menu, window, cx)\n        } else {\n            menu\n        }\n    }\n\n    fn toolbar_buttons(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Option<Vec<Button>> {\n        self.active_panel(cx)\n            .and_then(|panel| panel.toolbar_buttons(window, cx))\n    }\n\n    fn dump(&self, cx: &App) -> PanelState {\n        let mut state = PanelState::new(self);\n        for panel in self.panels.iter() {\n            state.add_child(panel.dump(cx));\n            state.info = PanelInfo::tabs(self.active_ix);\n        }\n        state\n    }\n\n    fn inner_padding(&self, cx: &App) -> bool {\n        self.active_panel(cx)\n            .map_or(true, |panel| panel.inner_padding(cx))\n    }\n}\n\nimpl TabPanel {\n    pub fn new(\n        stack_panel: Option<WeakEntity<StackPanel>>,\n        dock_area: WeakEntity<DockArea>,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            dock_area,\n            stack_panel,\n            panels: Vec::new(),\n            active_ix: 0,\n            tab_bar_scroll_handle: ScrollHandle::new(),\n            will_split_placement: None,\n            zoomed: false,\n            collapsed: false,\n            closable: true,\n            in_tiles: false,\n        }\n    }\n\n    /// Mark the TabPanel as being used in Tiles.\n    pub(super) fn set_in_tiles(&mut self, in_tiles: bool) {\n        self.in_tiles = in_tiles;\n    }\n\n    pub(super) fn set_parent(&mut self, view: WeakEntity<StackPanel>) {\n        self.stack_panel = Some(view);\n    }\n\n    /// Return current active_panel View\n    pub fn active_panel(&self, cx: &App) -> Option<Arc<dyn PanelView>> {\n        let panel = self.panels.get(self.active_ix);\n\n        if let Some(panel) = panel {\n            if panel.visible(cx) {\n                Some(panel.clone())\n            } else {\n                // Return the first visible panel\n                self.visible_panels(cx).next()\n            }\n        } else {\n            None\n        }\n    }\n\n    pub fn active_ix(&self) -> usize {\n        self.active_ix\n    }\n\n    fn set_active_ix(&mut self, ix: usize, window: &mut Window, cx: &mut Context<Self>) {\n        if ix == self.active_ix {\n            return;\n        }\n\n        let last_active_ix = self.active_ix;\n\n        self.active_ix = ix;\n        self.tab_bar_scroll_handle.scroll_to_item(ix);\n        self.focus_active_panel(window, cx);\n\n        // Sync the active state to all panels\n        cx.spawn_in(window, async move |view, cx| {\n            _ = cx.update(|window, cx| {\n                _ = view.update(cx, |view, cx| {\n                    if let Some(last_active) = view.panels.get(last_active_ix) {\n                        last_active.set_active(false, window, cx);\n                    }\n                    if let Some(active) = view.panels.get(view.active_ix) {\n                        active.set_active(true, window, cx);\n                    }\n                });\n            });\n        })\n        .detach();\n\n        cx.emit(PanelEvent::LayoutChanged);\n        cx.notify();\n    }\n\n    /// Add a panel to the end of the tabs\n    pub fn add_panel(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.add_panel_with_active(panel, true, window, cx);\n    }\n\n    fn add_panel_with_active(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        active: bool,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        assert_ne!(\n            panel.panel_name(cx),\n            \"StackPanel\",\n            \"can not allows add `StackPanel` to `TabPanel`\"\n        );\n\n        if self\n            .panels\n            .iter()\n            .any(|p| p.view().entity_id() == panel.view().entity_id())\n        {\n            return;\n        }\n\n        panel.on_added_to(cx.entity().downgrade(), window, cx);\n        self.panels.push(panel);\n        // set the active panel to the new panel\n        if active {\n            self.set_active_ix(self.panels.len() - 1, window, cx);\n        }\n        cx.emit(PanelEvent::LayoutChanged);\n        cx.notify();\n    }\n\n    /// Add panel to try to split\n    pub fn add_panel_at(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        placement: Placement,\n        size: Option<Pixels>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        cx.spawn_in(window, async move |view, cx| {\n            cx.update(|window, cx| {\n                view.update(cx, |view, cx| {\n                    view.will_split_placement = Some(placement);\n                    view.split_panel(panel, placement, size, window, cx)\n                })\n                .ok()\n            })\n            .ok()\n        })\n        .detach();\n        cx.emit(PanelEvent::LayoutChanged);\n        cx.notify();\n    }\n\n    fn insert_panel_at(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        ix: usize,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if self\n            .panels\n            .iter()\n            .any(|p| p.view().entity_id() == panel.view().entity_id())\n        {\n            return;\n        }\n\n        panel.on_added_to(cx.entity().downgrade(), window, cx);\n        self.panels.insert(ix, panel);\n        self.set_active_ix(ix, window, cx);\n        cx.emit(PanelEvent::LayoutChanged);\n        cx.notify();\n    }\n\n    /// Remove a panel from the tab panel\n    pub fn remove_panel(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.detach_panel(panel, window, cx);\n        self.remove_self_if_empty(window, cx);\n        cx.emit(PanelEvent::ZoomOut);\n        cx.emit(PanelEvent::LayoutChanged);\n    }\n\n    fn detach_panel(\n        &mut self,\n        panel: Arc<dyn PanelView>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        panel.on_removed(window, cx);\n        let panel_view = panel.view();\n        self.panels.retain(|p| p.view() != panel_view);\n        if self.active_ix >= self.panels.len() {\n            self.set_active_ix(self.panels.len().saturating_sub(1), window, cx)\n        }\n    }\n\n    /// Check to remove self from the parent StackPanel, if there is no panel left\n    fn remove_self_if_empty(&self, window: &mut Window, cx: &mut Context<Self>) {\n        if !self.panels.is_empty() {\n            return;\n        }\n\n        let tab_view = cx.entity().clone();\n        if let Some(stack_panel) = self.stack_panel.as_ref() {\n            _ = stack_panel.update(cx, |view, cx| {\n                view.remove_panel(Arc::new(tab_view), window, cx);\n            });\n        }\n    }\n\n    pub(super) fn set_collapsed(\n        &mut self,\n        collapsed: bool,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.collapsed = collapsed;\n        if let Some(panel) = self.panels.get(self.active_ix) {\n            panel.set_active(!collapsed, window, cx);\n        }\n        cx.notify();\n    }\n\n    fn is_locked(&self, cx: &App) -> bool {\n        let Some(dock_area) = self.dock_area.upgrade() else {\n            return true;\n        };\n\n        if dock_area.read(cx).is_locked() {\n            return true;\n        }\n\n        if self.zoomed {\n            return true;\n        }\n\n        self.stack_panel.is_none()\n    }\n\n    /// Return true if self or parent only have last panel.\n    fn is_last_panel(&self, cx: &App) -> bool {\n        if let Some(parent) = &self.stack_panel {\n            if let Some(stack_panel) = parent.upgrade() {\n                if !stack_panel.read(cx).is_last_panel(cx) {\n                    return false;\n                }\n            }\n        }\n\n        self.panels.len() <= 1\n    }\n\n    /// Return all visible panels\n    fn visible_panels<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = Arc<dyn PanelView>> + 'a {\n        self.panels.iter().filter_map(|panel| {\n            if panel.visible(cx) {\n                Some(panel.clone())\n            } else {\n                None\n            }\n        })\n    }\n\n    /// Return true if the tab panel is draggable.\n    ///\n    /// E.g. if the parent and self only have one panel, it is not draggable.\n    fn draggable(&self, cx: &App) -> bool {\n        !self.is_locked(cx) && !self.is_last_panel(cx)\n    }\n\n    /// Return true if the tab panel is droppable.\n    ///\n    /// E.g. if the tab panel is locked, it is not droppable.\n    fn droppable(&self, cx: &App) -> bool {\n        !self.is_locked(cx)\n    }\n\n    fn render_toolbar(\n        &mut self,\n        state: &TabState,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> impl IntoElement {\n        if self.collapsed {\n            return div();\n        }\n\n        let zoomed = self.zoomed;\n        let view = cx.entity().clone();\n        let zoomable_toolbar_visible = state.zoomable.map_or(false, |v| v.toolbar_visible());\n\n        h_flex()\n            .gap_1()\n            .occlude()\n            .when_some(self.toolbar_buttons(window, cx), |this, buttons| {\n                this.children(\n                    buttons\n                        .into_iter()\n                        .map(|btn| btn.xsmall().ghost().tab_stop(false)),\n                )\n            })\n            .map(|this| {\n                let value = if zoomed {\n                    Some((\"zoom-out\", IconName::Minimize, t!(\"Dock.Zoom Out\")))\n                } else if zoomable_toolbar_visible {\n                    Some((\"zoom-in\", IconName::Maximize, t!(\"Dock.Zoom In\")))\n                } else {\n                    None\n                };\n\n                if let Some((id, icon, tooltip)) = value {\n                    this.child(\n                        Button::new(id)\n                            .icon(icon)\n                            .xsmall()\n                            .ghost()\n                            .tab_stop(false)\n                            .tooltip_with_action(tooltip, &ToggleZoom, None)\n                            .when(zoomed, |this| this.selected(true))\n                            .on_click(cx.listener(|view, _, window, cx| {\n                                view.on_action_toggle_zoom(&ToggleZoom, window, cx)\n                            })),\n                    )\n                } else {\n                    this\n                }\n            })\n            .child(\n                Button::new(\"menu\")\n                    .icon(IconName::Ellipsis)\n                    .xsmall()\n                    .ghost()\n                    .tab_stop(false)\n                    .dropdown_menu({\n                        let zoomable = state.zoomable.map_or(false, |v| v.menu_visible());\n                        let closable = state.closable;\n\n                        move |menu, window, cx| {\n                            view.update(cx, |this, cx| {\n                                this.dropdown_menu(menu, window, cx)\n                                    .separator()\n                                    .menu_with_disabled(\n                                        if zoomed {\n                                            t!(\"Dock.Zoom Out\")\n                                        } else {\n                                            t!(\"Dock.Zoom In\")\n                                        },\n                                        Box::new(ToggleZoom),\n                                        !zoomable,\n                                    )\n                                    .when(closable, |this| {\n                                        this.separator()\n                                            .menu(t!(\"Dock.Close\"), Box::new(ClosePanel))\n                                    })\n                            })\n                        }\n                    })\n                    .anchor(Corner::TopRight),\n            )\n    }\n\n    fn render_dock_toggle_button(\n        &self,\n        placement: DockPlacement,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Option<Button> {\n        if self.zoomed {\n            return None;\n        }\n\n        let dock_area = self.dock_area.upgrade()?.read(cx);\n        if !dock_area.toggle_button_visible {\n            return None;\n        }\n        if !dock_area.is_dock_collapsible(placement, cx) {\n            return None;\n        }\n\n        let view_entity_id = cx.entity().entity_id();\n        let toggle_button_panels = dock_area.toggle_button_panels;\n\n        // Check if current TabPanel's entity_id matches the one stored in DockArea for this placement\n        if !match placement {\n            DockPlacement::Left => {\n                dock_area.left_dock.is_some() && toggle_button_panels.left == Some(view_entity_id)\n            }\n            DockPlacement::Right => {\n                dock_area.right_dock.is_some() && toggle_button_panels.right == Some(view_entity_id)\n            }\n            DockPlacement::Bottom => {\n                dock_area.bottom_dock.is_some()\n                    && toggle_button_panels.bottom == Some(view_entity_id)\n            }\n            DockPlacement::Center => unreachable!(),\n        } {\n            return None;\n        }\n\n        let is_open = dock_area.is_dock_open(placement, cx);\n\n        let icon = match placement {\n            DockPlacement::Left => {\n                if is_open {\n                    IconName::PanelLeft\n                } else {\n                    IconName::PanelLeftOpen\n                }\n            }\n            DockPlacement::Right => {\n                if is_open {\n                    IconName::PanelRight\n                } else {\n                    IconName::PanelRightOpen\n                }\n            }\n            DockPlacement::Bottom => {\n                if is_open {\n                    IconName::PanelBottom\n                } else {\n                    IconName::PanelBottomOpen\n                }\n            }\n            DockPlacement::Center => unreachable!(),\n        };\n\n        Some(\n            Button::new(SharedString::from(format!(\"toggle-dock:{:?}\", placement)))\n                .icon(icon)\n                .xsmall()\n                .ghost()\n                .tab_stop(false)\n                .tooltip(match is_open {\n                    true => t!(\"Dock.Collapse\"),\n                    false => t!(\"Dock.Expand\"),\n                })\n                .on_click(cx.listener({\n                    let dock_area = self.dock_area.clone();\n                    move |_, _, window, cx| {\n                        _ = dock_area.update(cx, |dock_area, cx| {\n                            dock_area.toggle_dock(placement, window, cx);\n                        });\n                    }\n                })),\n        )\n    }\n\n    fn render_title_bar(\n        &mut self,\n        state: &TabState,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> impl IntoElement {\n        let view = cx.entity().clone();\n\n        let Some(dock_area) = self.dock_area.upgrade() else {\n            return div().into_any_element();\n        };\n\n        let left_dock_button = self.render_dock_toggle_button(DockPlacement::Left, window, cx);\n        let bottom_dock_button = self.render_dock_toggle_button(DockPlacement::Bottom, window, cx);\n        let right_dock_button = self.render_dock_toggle_button(DockPlacement::Right, window, cx);\n        let has_extend_dock_button = left_dock_button.is_some() || bottom_dock_button.is_some();\n\n        let is_bottom_dock = bottom_dock_button.is_some();\n\n        let panel_style = dock_area.read(cx).panel_style;\n        let visible_panels = self.visible_panels(cx).collect::<Vec<_>>();\n\n        if visible_panels.len() == 1 && panel_style == PanelStyle::default() {\n            let panel = visible_panels.get(0).unwrap();\n\n            if !panel.visible(cx) {\n                return div().into_any_element();\n            }\n\n            let title_style = panel.title_style(cx);\n\n            return h_flex()\n                .justify_between()\n                .line_height(rems(1.0))\n                .h(px(30.))\n                .py_2()\n                .pl_3()\n                .pr_2()\n                .when(left_dock_button.is_some(), |this| this.pl_2())\n                .when(right_dock_button.is_some(), |this| this.pr_2())\n                .when_some(title_style, |this, theme| {\n                    this.bg(theme.background).text_color(theme.foreground)\n                })\n                .when(has_extend_dock_button, |this| {\n                    this.child(\n                        h_flex()\n                            .flex_shrink_0()\n                            .mr_1()\n                            .gap_1()\n                            .children(left_dock_button)\n                            .children(bottom_dock_button),\n                    )\n                })\n                .child(\n                    div()\n                        .id(\"tab\")\n                        .flex_1()\n                        .min_w_16()\n                        .overflow_hidden()\n                        .text_ellipsis()\n                        .whitespace_nowrap()\n                        .child(panel.title(window, cx))\n                        .when(state.draggable, |this| {\n                            this.on_drag(\n                                DragPanel {\n                                    panel: panel.clone(),\n                                    tab_panel: view,\n                                },\n                                |drag, _, _, cx| {\n                                    cx.stop_propagation();\n                                    cx.new(|_| drag.clone())\n                                },\n                            )\n                        }),\n                )\n                .children(panel.title_suffix(window, cx))\n                .child(\n                    h_flex()\n                        .flex_shrink_0()\n                        .ml_1()\n                        .gap_1()\n                        .child(self.render_toolbar(&state, window, cx))\n                        .children(right_dock_button),\n                )\n                .into_any_element();\n        }\n\n        let tabs_count = self.panels.len();\n\n        TabBar::new(\"tab-bar\")\n            .track_scroll(&self.tab_bar_scroll_handle)\n            .when(has_extend_dock_button, |this| {\n                this.prefix(\n                    h_flex()\n                        .items_center()\n                        .top_0()\n                        // Right -1 for avoid border overlap with the first tab\n                        .right(-px(1.))\n                        .border_r_1()\n                        .border_b_1()\n                        .h_full()\n                        .border_color(cx.theme().border)\n                        .bg(cx.theme().tab_bar)\n                        .px_2()\n                        .children(left_dock_button)\n                        .children(bottom_dock_button),\n                )\n            })\n            .children(self.panels.iter().enumerate().filter_map(|(ix, panel)| {\n                let mut active = state.active_panel.as_ref() == Some(panel);\n                let droppable = self.collapsed;\n\n                if !panel.visible(cx) {\n                    return None;\n                }\n\n                // Always not show active tab style, if the panel is collapsed\n                if self.collapsed {\n                    active = false;\n                }\n\n                Some(\n                    Tab::new()\n                        .ix(ix)\n                        .tab_bar_prefix(has_extend_dock_button)\n                        .map(|this| {\n                            if let Some(tab_name) = panel.tab_name(cx) {\n                                this.child(tab_name)\n                            } else {\n                                this.child(panel.title(window, cx))\n                            }\n                        })\n                        .selected(active)\n                        .on_click(cx.listener({\n                            let is_collapsed = self.collapsed;\n                            let dock_area = self.dock_area.clone();\n                            move |view, _, window, cx| {\n                                view.set_active_ix(ix, window, cx);\n\n                                // Open dock if clicked on the collapsed bottom dock\n                                if is_bottom_dock && is_collapsed {\n                                    _ = dock_area.update(cx, |dock_area, cx| {\n                                        dock_area.toggle_dock(DockPlacement::Bottom, window, cx);\n                                    });\n                                }\n                            }\n                        }))\n                        .when(!droppable, |this| {\n                            this.when(state.draggable, |this| {\n                                this.on_drag(\n                                    DragPanel::new(panel.clone(), view.clone()),\n                                    |drag, _, _, cx| {\n                                        cx.stop_propagation();\n                                        cx.new(|_| drag.clone())\n                                    },\n                                )\n                            })\n                            .when(state.droppable, |this| {\n                                this.drag_over::<DragPanel>(|this, _, _, cx| {\n                                    this.rounded_l_none()\n                                        .border_l_2()\n                                        .border_r_0()\n                                        .border_color(cx.theme().drag_border)\n                                })\n                                .on_drop(cx.listener(\n                                    move |this, drag: &DragPanel, window, cx| {\n                                        this.will_split_placement = None;\n                                        this.on_drop(drag, Some(ix), true, window, cx)\n                                    },\n                                ))\n                            })\n                        }),\n                )\n            }))\n            .last_empty_space(\n                // empty space to allow move to last tab right\n                div()\n                    .id(\"tab-bar-empty-space\")\n                    .h_full()\n                    .flex_grow()\n                    .min_w_16()\n                    .when(state.droppable, |this| {\n                        this.drag_over::<DragPanel>(|this, _, _, cx| {\n                            this.bg(cx.theme().drop_target)\n                        })\n                        .on_drop(cx.listener(\n                            move |this, drag: &DragPanel, window, cx| {\n                                this.will_split_placement = None;\n\n                                let ix = if drag.tab_panel == view {\n                                    Some(tabs_count - 1)\n                                } else {\n                                    None\n                                };\n\n                                this.on_drop(drag, ix, false, window, cx)\n                            },\n                        ))\n                    }),\n            )\n            .when(!self.collapsed, |this| {\n                this.suffix(\n                    h_flex()\n                        .items_center()\n                        .top_0()\n                        .right_0()\n                        .border_l_1()\n                        .border_b_1()\n                        .h_full()\n                        .border_color(cx.theme().border)\n                        .bg(cx.theme().tab_bar)\n                        .px_2()\n                        .gap_1()\n                        .children(\n                            self.active_panel(cx)\n                                .and_then(|panel| panel.title_suffix(window, cx)),\n                        )\n                        .child(self.render_toolbar(state, window, cx))\n                        .when_some(right_dock_button, |this, btn| this.child(btn)),\n                )\n            })\n            .into_any_element()\n    }\n\n    fn render_active_panel(\n        &self,\n        state: &TabState,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> impl IntoElement {\n        if self.collapsed {\n            return Empty {}.into_any_element();\n        }\n\n        let Some(active_panel) = state.active_panel.as_ref() else {\n            return Empty {}.into_any_element();\n        };\n\n        let is_render_in_tabs = self.panels.len() > 1 && self.inner_padding(cx);\n\n        v_flex()\n            .id(\"active-panel\")\n            .group(\"\")\n            .flex_1()\n            .when(is_render_in_tabs, |this| this.pt_2())\n            .child(\n                div()\n                    .id(\"tab-content\")\n                    .overflow_y_scroll()\n                    .overflow_x_hidden()\n                    .flex_1()\n                    .child(\n                        active_panel\n                            .view()\n                            .cached(StyleRefinement::default().absolute().size_full()),\n                    ),\n            )\n            .when(state.droppable, |this| {\n                this.on_drag_move(cx.listener(Self::on_panel_drag_move))\n                    .child(\n                        div()\n                            .invisible()\n                            .absolute()\n                            .bg(cx.theme().drop_target)\n                            .map(|this| match self.will_split_placement {\n                                Some(placement) => {\n                                    let size = relative(0.5);\n                                    match placement {\n                                        Placement::Left => this.left_0().top_0().bottom_0().w(size),\n                                        Placement::Right => {\n                                            this.right_0().top_0().bottom_0().w(size)\n                                        }\n                                        Placement::Top => this.top_0().left_0().right_0().h(size),\n                                        Placement::Bottom => {\n                                            this.bottom_0().left_0().right_0().h(size)\n                                        }\n                                    }\n                                }\n                                None => this.top_0().left_0().size_full(),\n                            })\n                            .group_drag_over::<DragPanel>(\"\", |this| this.visible())\n                            .on_drop(cx.listener(|this, drag: &DragPanel, window, cx| {\n                                this.on_drop(drag, None, true, window, cx)\n                            })),\n                    )\n            })\n            .into_any_element()\n    }\n\n    /// Calculate the split direction based on the current mouse position\n    fn on_panel_drag_move(\n        &mut self,\n        drag: &DragMoveEvent<DragPanel>,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let bounds = drag.bounds;\n        let position = drag.event.position;\n\n        // Check the mouse position to determine the split direction\n        if position.x < bounds.left() + bounds.size.width * 0.35 {\n            self.will_split_placement = Some(Placement::Left);\n        } else if position.x > bounds.left() + bounds.size.width * 0.65 {\n            self.will_split_placement = Some(Placement::Right);\n        } else if position.y < bounds.top() + bounds.size.height * 0.35 {\n            self.will_split_placement = Some(Placement::Top);\n        } else if position.y > bounds.top() + bounds.size.height * 0.65 {\n            self.will_split_placement = Some(Placement::Bottom);\n        } else {\n            // center to merge into the current tab\n            self.will_split_placement = None;\n        }\n        cx.notify()\n    }\n\n    /// Handle the drop event when dragging a panel\n    ///\n    /// - `active` - When true, the panel will be active after the drop\n    fn on_drop(\n        &mut self,\n        drag: &DragPanel,\n        ix: Option<usize>,\n        active: bool,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let panel = drag.panel.clone();\n        let is_same_tab = drag.tab_panel == cx.entity();\n\n        // If target is same tab, and it is only one panel, do nothing.\n        if is_same_tab && ix.is_none() {\n            if self.will_split_placement.is_none() {\n                return;\n            } else {\n                if self.panels.len() == 1 {\n                    return;\n                }\n            }\n        }\n\n        // Here is looks like remove_panel on a same item, but it difference.\n        //\n        // We must to split it to remove_panel, unless it will be crash by error:\n        // Cannot update ui::dock::tab_panel::TabPanel while it is already being updated\n        if is_same_tab {\n            self.detach_panel(panel.clone(), window, cx);\n        } else {\n            let _ = drag.tab_panel.update(cx, |view, cx| {\n                view.detach_panel(panel.clone(), window, cx);\n                view.remove_self_if_empty(window, cx);\n            });\n        }\n\n        // Insert into new tabs\n        if let Some(placement) = self.will_split_placement {\n            self.split_panel(panel, placement, None, window, cx);\n        } else {\n            if let Some(ix) = ix {\n                self.insert_panel_at(panel, ix, window, cx)\n            } else {\n                self.add_panel_with_active(panel, active, window, cx)\n            }\n        }\n\n        self.remove_self_if_empty(window, cx);\n        cx.emit(PanelEvent::LayoutChanged);\n    }\n\n    /// Add panel with split placement\n    fn split_panel(\n        &self,\n        panel: Arc<dyn PanelView>,\n        placement: Placement,\n        size: Option<Pixels>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let dock_area = self.dock_area.clone();\n        // wrap the panel in a TabPanel\n        let new_tab_panel = cx.new(|cx| Self::new(None, dock_area.clone(), window, cx));\n        new_tab_panel.update(cx, |view, cx| {\n            view.add_panel(panel, window, cx);\n        });\n\n        let stack_panel = match self.stack_panel.as_ref().and_then(|panel| panel.upgrade()) {\n            Some(panel) => panel,\n            None => return,\n        };\n\n        let parent_axis = stack_panel.read(cx).axis;\n\n        let ix = stack_panel\n            .read(cx)\n            .index_of_panel(Arc::new(cx.entity().clone()))\n            .unwrap_or_default();\n\n        if parent_axis.is_vertical() && placement.is_vertical() {\n            stack_panel.update(cx, |view, cx| {\n                view.insert_panel_at(\n                    Arc::new(new_tab_panel),\n                    ix,\n                    placement,\n                    size,\n                    dock_area.clone(),\n                    window,\n                    cx,\n                );\n            });\n        } else if parent_axis.is_horizontal() && placement.is_horizontal() {\n            stack_panel.update(cx, |view, cx| {\n                view.insert_panel_at(\n                    Arc::new(new_tab_panel),\n                    ix,\n                    placement,\n                    size,\n                    dock_area.clone(),\n                    window,\n                    cx,\n                );\n            });\n        } else {\n            // 1. Create new StackPanel with new axis\n            // 2. Move cx.entity() from parent StackPanel to the new StackPanel\n            // 3. Add the new TabPanel to the new StackPanel at the correct index\n            // 4. Add new StackPanel to the parent StackPanel at the correct index\n            let tab_panel = cx.entity().clone();\n\n            // Try to use the old stack panel, not just create a new one, to avoid too many nested stack panels\n            let new_stack_panel = if stack_panel.read(cx).panels_len() <= 1 {\n                stack_panel.update(cx, |view, cx| {\n                    view.remove_all_panels(window, cx);\n                    view.set_axis(placement.axis(), window, cx);\n                });\n                stack_panel.clone()\n            } else {\n                cx.new(|cx| {\n                    let mut panel = StackPanel::new(placement.axis(), window, cx);\n                    panel.parent = Some(stack_panel.downgrade());\n                    panel\n                })\n            };\n\n            new_stack_panel.update(cx, |view, cx| match placement {\n                Placement::Left | Placement::Top => {\n                    view.add_panel(Arc::new(new_tab_panel), size, dock_area.clone(), window, cx);\n                    view.add_panel(\n                        Arc::new(tab_panel.clone()),\n                        None,\n                        dock_area.clone(),\n                        window,\n                        cx,\n                    );\n                }\n                Placement::Right | Placement::Bottom => {\n                    view.add_panel(\n                        Arc::new(tab_panel.clone()),\n                        None,\n                        dock_area.clone(),\n                        window,\n                        cx,\n                    );\n                    view.add_panel(Arc::new(new_tab_panel), size, dock_area.clone(), window, cx);\n                }\n            });\n\n            if stack_panel != new_stack_panel {\n                stack_panel.update(cx, |view, cx| {\n                    view.replace_panel(\n                        Arc::new(tab_panel.clone()),\n                        new_stack_panel.clone(),\n                        window,\n                        cx,\n                    );\n                });\n            }\n\n            cx.spawn_in(window, async move |_, cx| {\n                cx.update(|window, cx| {\n                    tab_panel.update(cx, |view, cx| view.remove_self_if_empty(window, cx))\n                })\n            })\n            .detach()\n        }\n\n        cx.emit(PanelEvent::LayoutChanged);\n    }\n\n    fn focus_active_panel(&self, window: &mut Window, cx: &mut Context<Self>) {\n        if let Some(active_panel) = self.active_panel(cx) {\n            active_panel.focus_handle(cx).focus(window, cx);\n        }\n    }\n\n    fn on_action_toggle_zoom(\n        &mut self,\n        _: &ToggleZoom,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if self.zoomable(cx).is_none() {\n            return;\n        }\n\n        if !self.zoomed {\n            cx.emit(PanelEvent::ZoomIn)\n        } else {\n            cx.emit(PanelEvent::ZoomOut)\n        }\n        self.zoomed = !self.zoomed;\n\n        cx.spawn_in(window, {\n            let zoomed = self.zoomed;\n            async move |view, cx| {\n                _ = cx.update(|window, cx| {\n                    _ = view.update(cx, |view, cx| {\n                        view.set_zoomed(zoomed, window, cx);\n                    });\n                });\n            }\n        })\n        .detach();\n    }\n\n    fn on_action_close_panel(\n        &mut self,\n        _: &ClosePanel,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if !self.closable(cx) {\n            return;\n        }\n        if let Some(panel) = self.active_panel(cx) {\n            self.remove_panel(panel, window, cx);\n        }\n\n        // Remove self from the parent DockArea.\n        // This is ensure to remove from Tiles\n        if self.panels.is_empty() && self.in_tiles {\n            let tab_panel = Arc::new(cx.entity());\n            window.defer(cx, {\n                let dock_area = self.dock_area.clone();\n                move |window, cx| {\n                    _ = dock_area.update(cx, |this, cx| {\n                        this.remove_panel_from_all_docks(tab_panel, window, cx);\n                    });\n                }\n            });\n        }\n    }\n\n    // Bind actions to the tab panel, only when the tab panel is not collapsed.\n    fn bind_actions(&self, cx: &mut Context<Self>) -> Div {\n        v_flex().when(!self.collapsed, |this| {\n            this.on_action(cx.listener(Self::on_action_toggle_zoom))\n                .on_action(cx.listener(Self::on_action_close_panel))\n        })\n    }\n}\n\nimpl Focusable for TabPanel {\n    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {\n        if let Some(active_panel) = self.active_panel(cx) {\n            active_panel.focus_handle(cx)\n        } else {\n            self.focus_handle.clone()\n        }\n    }\n}\nimpl EventEmitter<DismissEvent> for TabPanel {}\nimpl EventEmitter<PanelEvent> for TabPanel {}\nimpl Render for TabPanel {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {\n        let focus_handle = self.focus_handle(cx);\n        let active_panel = self.active_panel(cx);\n        let state = TabState {\n            closable: self.closable(cx),\n            draggable: self.draggable(cx),\n            droppable: self.droppable(cx),\n            zoomable: self.zoomable(cx),\n            active_panel,\n        };\n\n        self.bind_actions(cx)\n            .id(\"tab-panel\")\n            .track_focus(&focus_handle)\n            .tab_group()\n            .size_full()\n            .overflow_hidden()\n            .bg(cx.theme().background)\n            .child(self.render_title_bar(&state, window, cx))\n            .child(self.render_active_panel(&state, window, cx))\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/dock/tiles.rs",
    "content": "use std::{\n    any::Any,\n    fmt::{Debug, Formatter},\n    sync::Arc,\n};\n\nuse crate::{\n    ActiveTheme, ElementExt, Icon, IconName, h_flex,\n    history::{History, HistoryItem},\n    scroll::{Scrollbar, ScrollbarShow},\n    v_flex,\n};\n\nuse super::{\n    DockArea, Panel, PanelEvent, PanelInfo, PanelState, PanelView, StackPanel, TabPanel, TileMeta,\n};\nuse gpui::{\n    AnyElement, App, AppContext, Bounds, Context, DismissEvent, Div, DragMoveEvent, Empty,\n    EntityId, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, MouseButton,\n    MouseDownEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollHandle, Size,\n    StatefulInteractiveElement, Styled, WeakEntity, Window, actions, div, prelude::FluentBuilder,\n    px, size,\n};\n\nactions!(tiles, [Undo, Redo]);\n\nconst MINIMUM_SIZE: Size<Pixels> = size(px(100.), px(100.));\nconst DRAG_BAR_HEIGHT: Pixels = px(30.);\nconst HANDLE_SIZE: Pixels = px(5.0);\n\n#[derive(Clone, PartialEq, Debug)]\nstruct TileChange {\n    tile_id: EntityId,\n    old_bounds: Option<Bounds<Pixels>>,\n    new_bounds: Option<Bounds<Pixels>>,\n    old_order: Option<usize>,\n    new_order: Option<usize>,\n    version: usize,\n}\n\nimpl HistoryItem for TileChange {\n    fn version(&self) -> usize {\n        self.version\n    }\n\n    fn set_version(&mut self, version: usize) {\n        self.version = version;\n    }\n}\n\n#[derive(Clone)]\npub struct DragMoving(EntityId);\nimpl Render for DragMoving {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        Empty\n    }\n}\n\n#[derive(Clone, PartialEq)]\nenum ResizeSide {\n    Left,\n    Right,\n    Top,\n    Bottom,\n    BottomRight,\n}\n\n#[derive(Clone)]\npub struct DragResizing(EntityId);\n\nimpl Render for DragResizing {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        Empty\n    }\n}\n\n#[derive(Clone)]\nstruct ResizeDrag {\n    side: ResizeSide,\n    last_position: Point<Pixels>,\n    last_bounds: Bounds<Pixels>,\n}\n\n/// TileItem is a moveable and resizable panel that can be added to a Tiles view.\n#[derive(Clone)]\npub struct TileItem {\n    id: EntityId,\n    pub(crate) panel: Arc<dyn PanelView>,\n    bounds: Bounds<Pixels>,\n    z_index: usize,\n}\n\nimpl Debug for TileItem {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"TileItem\")\n            .field(\"bounds\", &self.bounds)\n            .field(\"z_index\", &self.z_index)\n            .finish()\n    }\n}\n\nimpl TileItem {\n    pub fn new(panel: Arc<dyn PanelView>, bounds: Bounds<Pixels>) -> Self {\n        Self {\n            id: panel.view().entity_id(),\n            panel,\n            bounds,\n            z_index: 0,\n        }\n    }\n\n    pub fn z_index(mut self, z_index: usize) -> Self {\n        self.z_index = z_index;\n        self\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct AnyDrag {\n    pub value: Arc<dyn Any>,\n}\n\nimpl AnyDrag {\n    pub fn new(value: impl Any) -> Self {\n        Self {\n            value: Arc::new(value),\n        }\n    }\n}\n\n/// Tiles is a canvas that can contain multiple panels, each of which can be dragged and resized.\npub struct Tiles {\n    focus_handle: FocusHandle,\n    pub(crate) panels: Vec<TileItem>,\n    dragging_id: Option<EntityId>,\n    dragging_initial_mouse: Point<Pixels>,\n    dragging_initial_bounds: Bounds<Pixels>,\n    resizing_id: Option<EntityId>,\n    resizing_drag_data: Option<ResizeDrag>,\n    bounds: Bounds<Pixels>,\n    history: History<TileChange>,\n    scroll_handle: ScrollHandle,\n    scrollbar_show: Option<ScrollbarShow>,\n}\n\nimpl Panel for Tiles {\n    fn panel_name(&self) -> &'static str {\n        \"Tiles\"\n    }\n\n    fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {\n        \"Tiles\".into_any_element()\n    }\n\n    fn dump(&self, cx: &App) -> PanelState {\n        let panels = self\n            .panels\n            .iter()\n            .map(|item: &TileItem| item.panel.dump(cx))\n            .collect();\n\n        let metas = self\n            .panels\n            .iter()\n            .map(|item: &TileItem| TileMeta {\n                bounds: item.bounds,\n                z_index: item.z_index,\n            })\n            .collect();\n\n        let mut state = PanelState::new(self);\n        state.panel_name = self.panel_name().to_string();\n        state.children = panels;\n        state.info = PanelInfo::Tiles { metas };\n        state\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct DragDrop(pub AnyDrag);\n\nimpl EventEmitter<DragDrop> for Tiles {}\n\nimpl Tiles {\n    pub fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            panels: vec![],\n            dragging_id: None,\n            dragging_initial_mouse: Point::default(),\n            dragging_initial_bounds: Bounds::default(),\n            resizing_id: None,\n            scrollbar_show: None,\n            resizing_drag_data: None,\n            bounds: Bounds::default(),\n            history: History::new().group_interval(std::time::Duration::from_millis(100)),\n            scroll_handle: ScrollHandle::default(),\n        }\n    }\n\n    /// Set the scrollbar show mode [`ScrollbarShow`], if not set use the `cx.theme().scrollbar_show`.\n    pub fn set_scrollbar_show(\n        &mut self,\n        scrollbar_show: Option<ScrollbarShow>,\n        cx: &mut Context<Self>,\n    ) {\n        self.scrollbar_show = scrollbar_show;\n        cx.notify();\n    }\n\n    pub fn panels(&self) -> &[TileItem] {\n        &self.panels\n    }\n\n    fn sorted_panels(&self) -> Vec<TileItem> {\n        let mut items: Vec<(usize, TileItem)> = self.panels.iter().cloned().enumerate().collect();\n        items.sort_by(|a, b| a.1.z_index.cmp(&b.1.z_index).then_with(|| a.0.cmp(&b.0)));\n        items.into_iter().map(|(_, item)| item).collect()\n    }\n\n    /// Return the index of the panel.\n    #[inline]\n    pub(crate) fn index_of(&self, id: &EntityId) -> Option<usize> {\n        self.panels.iter().position(|p| &p.id == id)\n    }\n\n    #[inline]\n    pub(crate) fn panel(&self, id: &EntityId) -> Option<&TileItem> {\n        self.panels.iter().find(|p| &p.id == id)\n    }\n\n    /// Remove panel from the children.\n    pub fn remove(&mut self, panel: Arc<dyn PanelView>, _: &mut Window, cx: &mut Context<Self>) {\n        if let Some(ix) = self.index_of(&panel.panel_id(cx)) {\n            self.panels.remove(ix);\n\n            cx.emit(PanelEvent::LayoutChanged);\n        }\n    }\n\n    /// Calculate magnetic snap position for the dragging panel\n    fn calculate_magnetic_snap(\n        &self,\n        dragging_bounds: Bounds<Pixels>,\n        item_ix: usize,\n        snap_threshold: Pixels,\n    ) -> (Option<Pixels>, Option<Pixels>) {\n        // Only check nearby panels\n        let search_bounds = Bounds {\n            origin: Point {\n                x: dragging_bounds.left() - snap_threshold,\n                y: dragging_bounds.top() - snap_threshold,\n            },\n            size: Size {\n                width: dragging_bounds.size.width + snap_threshold * 2.0,\n                height: dragging_bounds.size.height + snap_threshold * 2.0,\n            },\n        };\n\n        let mut snap_x: Option<Pixels> = None;\n        let mut snap_y: Option<Pixels> = None;\n        let mut min_x_dist = snap_threshold;\n        let mut min_y_dist = snap_threshold;\n\n        // Pre-calculate dragging bounds edges to avoid repeated method calls\n        let drag_left = dragging_bounds.left();\n        let drag_right = dragging_bounds.right();\n        let drag_top = dragging_bounds.top();\n        let drag_bottom = dragging_bounds.bottom();\n        let drag_width = dragging_bounds.size.width;\n        let drag_height = dragging_bounds.size.height;\n\n        // Check for edge snapping first (top and left boundaries)\n        let edge_snap_pos = px(0.);\n\n        // Snap to top edge\n        let top_dist = drag_top.abs();\n        if top_dist < snap_threshold {\n            snap_y = Some(edge_snap_pos);\n            min_y_dist = top_dist;\n        }\n\n        // Snap to left edge\n        let left_dist = drag_left.abs();\n        if left_dist < snap_threshold {\n            snap_x = Some(edge_snap_pos);\n            min_x_dist = left_dist;\n        }\n\n        // If both edges are snapped, return early\n        if snap_x.is_some() && snap_y.is_some() {\n            return (snap_x, snap_y);\n        }\n\n        for (ix, other) in self.panels.iter().enumerate() {\n            if ix == item_ix {\n                continue;\n            }\n\n            // Pre-calculate other bounds edges\n            let other_left = other.bounds.left();\n            let other_right = other.bounds.right();\n            let other_top = other.bounds.top();\n            let other_bottom = other.bounds.bottom();\n\n            // Skip panels that are far away\n            if other_right < search_bounds.left()\n                || other_left > search_bounds.right()\n                || other_bottom < search_bounds.top()\n                || other_top > search_bounds.bottom()\n            {\n                continue;\n            }\n\n            // Horizontal snapping (X axis) - find closest snap point\n            if snap_x.is_none() {\n                let candidates = [\n                    ((drag_left - other_left).abs(), other_left),\n                    ((drag_left - other_right).abs(), other_right),\n                    ((drag_right - other_left).abs(), other_left - drag_width),\n                    ((drag_right - other_right).abs(), other_right - drag_width),\n                ];\n\n                for (dist, snap_pos) in candidates {\n                    if dist < min_x_dist {\n                        min_x_dist = dist;\n                        snap_x = Some(snap_pos);\n                    }\n                }\n            }\n\n            // Vertical snapping (Y axis) - find closest snap point\n            if snap_y.is_none() {\n                let candidates = [\n                    ((drag_top - other_top).abs(), other_top),\n                    ((drag_top - other_bottom).abs(), other_bottom),\n                    ((drag_bottom - other_top).abs(), other_top - drag_height),\n                    (\n                        (drag_bottom - other_bottom).abs(),\n                        other_bottom - drag_height,\n                    ),\n                ];\n\n                for (dist, snap_pos) in candidates {\n                    if dist < min_y_dist {\n                        min_y_dist = dist;\n                        snap_y = Some(snap_pos);\n                    }\n                }\n            }\n\n            // Early exit if both axes are snapped\n            if snap_x.is_some() && snap_y.is_some() {\n                break;\n            }\n        }\n\n        (snap_x, snap_y)\n    }\n\n    /// Apply boundary constraints to the panel origin\n    fn apply_boundary_constraints(&self, mut origin: Point<Pixels>) -> Point<Pixels> {\n        // Top boundary\n        if origin.y < px(0.) {\n            origin.y = px(0.);\n        }\n\n        // Left boundary (allow partial off-screen but keep 64px visible)\n        let min_left = -self.dragging_initial_bounds.size.width + px(64.);\n        if origin.x < min_left {\n            origin.x = min_left;\n        }\n\n        origin\n    }\n\n    fn update_position(&mut self, mouse_position: Point<Pixels>, cx: &mut Context<Self>) {\n        let Some(dragging_id) = self.dragging_id else {\n            return;\n        };\n\n        let Some(item_ix) = self.panels.iter().position(|p| p.id == dragging_id) else {\n            return;\n        };\n\n        let previous_bounds = self.panels[item_ix].bounds;\n        let adjusted_position = mouse_position - self.bounds.origin;\n        let delta = adjusted_position - self.dragging_initial_mouse;\n        let mut new_origin = self.dragging_initial_bounds.origin + delta;\n\n        // Apply magnetic snap before boundary checks\n        let snap_threshold = cx.theme().tile_grid_size;\n        let dragging_bounds = Bounds {\n            origin: new_origin,\n            size: self.dragging_initial_bounds.size,\n        };\n\n        let (snap_x, snap_y) =\n            self.calculate_magnetic_snap(dragging_bounds, item_ix, snap_threshold);\n\n        // Apply snapping\n        if let Some(x) = snap_x {\n            new_origin.x = x;\n        }\n        if let Some(y) = snap_y {\n            new_origin.y = y;\n        }\n\n        // Apply boundary constraints after snapping\n        new_origin = self.apply_boundary_constraints(new_origin);\n\n        // Update position without grid rounding (smooth dragging)\n        if new_origin != previous_bounds.origin {\n            self.panels[item_ix].bounds.origin = new_origin;\n            let item = &self.panels[item_ix];\n            let bounds = item.bounds;\n            let entity_id = item.panel.view().entity_id();\n\n            if !self.history.ignore {\n                self.history.push(TileChange {\n                    tile_id: entity_id,\n                    old_bounds: Some(previous_bounds),\n                    new_bounds: Some(bounds),\n                    old_order: None,\n                    new_order: None,\n                    version: 0,\n                });\n            }\n            cx.notify();\n        }\n    }\n\n    fn resize(\n        &mut self,\n        new_x: Option<Pixels>,\n        new_y: Option<Pixels>,\n        new_width: Option<Pixels>,\n        new_height: Option<Pixels>,\n        _: &mut Window,\n        cx: &mut Context<'_, Self>,\n    ) {\n        let Some(resizing_id) = self.resizing_id else {\n            return;\n        };\n        let Some(item) = self.panels.iter_mut().find(|item| item.id == resizing_id) else {\n            return;\n        };\n\n        let previous_bounds = item.bounds;\n        let final_x = if let Some(x) = new_x {\n            round_to_nearest_ten(x, cx)\n        } else {\n            previous_bounds.origin.x\n        };\n        let final_y = if let Some(y) = new_y {\n            round_to_nearest_ten(y, cx)\n        } else {\n            previous_bounds.origin.y\n        };\n        let final_width = if let Some(width) = new_width {\n            round_to_nearest_ten(width, cx)\n        } else {\n            previous_bounds.size.width\n        };\n\n        let final_height = if let Some(height) = new_height {\n            round_to_nearest_ten(height, cx)\n        } else {\n            previous_bounds.size.height\n        };\n\n        // Only push to history if size has changed\n        if final_width != item.bounds.size.width\n            || final_height != item.bounds.size.height\n            || final_x != item.bounds.origin.x\n            || final_y != item.bounds.origin.y\n        {\n            item.bounds.origin.x = final_x;\n            item.bounds.origin.y = final_y;\n            item.bounds.size.width = final_width;\n            item.bounds.size.height = final_height;\n\n            // Only push if not during history operations\n            if !self.history.ignore {\n                self.history.push(TileChange {\n                    tile_id: item.panel.view().entity_id(),\n                    old_bounds: Some(previous_bounds),\n                    new_bounds: Some(item.bounds),\n                    old_order: None,\n                    new_order: None,\n                    version: 0,\n                });\n            }\n        }\n\n        cx.notify();\n    }\n\n    pub fn add_item(\n        &mut self,\n        item: TileItem,\n        dock_area: &WeakEntity<DockArea>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let Ok(tab_panel) = item.panel.view().downcast::<TabPanel>() else {\n            panic!(\"only allows to add TabPanel type\")\n        };\n\n        tab_panel.update(cx, |tab_panel, _| {\n            tab_panel.set_in_tiles(true);\n        });\n\n        self.panels.push(item.clone());\n        window.defer(cx, {\n            let panel = item.panel.clone();\n            let dock_area = dock_area.clone();\n\n            move |window, cx| {\n                // Subscribe to the panel's layout change event.\n                _ = dock_area.update(cx, |this, cx| {\n                    if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {\n                        this.subscribe_panel(&tab_panel, window, cx);\n                    }\n                });\n            }\n        });\n\n        cx.emit(PanelEvent::LayoutChanged);\n        cx.notify();\n    }\n\n    #[inline]\n    fn reset_current_index(&mut self) {\n        self.dragging_id = None;\n        self.resizing_id = None;\n    }\n\n    /// Bring the panel of target_index to front, returns (old_index, new_index) if successful\n    fn bring_to_front(\n        &mut self,\n        target_id: Option<EntityId>,\n        cx: &mut Context<Self>,\n    ) -> Option<EntityId> {\n        let Some(old_id) = target_id else {\n            return None;\n        };\n\n        let old_ix = self.panels.iter().position(|item| item.id == old_id)?;\n        if old_ix < self.panels.len() {\n            let item = self.panels.remove(old_ix);\n            self.panels.push(item);\n            let new_ix = self.panels.len() - 1;\n            let new_id = self.panels[new_ix].id;\n            self.history.push(TileChange {\n                tile_id: new_id,\n                old_bounds: None,\n                new_bounds: None,\n                old_order: Some(old_ix),\n                new_order: Some(new_ix),\n                version: 0,\n            });\n            cx.notify();\n            return Some(new_id);\n        }\n        None\n    }\n\n    /// Handle the undo action\n    pub fn undo(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        self.history.ignore = true;\n\n        if let Some(changes) = self.history.undo() {\n            for change in changes {\n                if let Some(index) = self\n                    .panels\n                    .iter()\n                    .position(|item| item.panel.view().entity_id() == change.tile_id)\n                {\n                    if let Some(old_bounds) = change.old_bounds {\n                        self.panels[index].bounds = old_bounds;\n                    }\n                    if let Some(old_order) = change.old_order {\n                        let item = self.panels.remove(index);\n                        self.panels.insert(old_order, item);\n                    }\n                }\n            }\n            cx.emit(PanelEvent::LayoutChanged);\n        }\n\n        self.history.ignore = false;\n        cx.notify();\n    }\n\n    /// Handle the redo action\n    pub fn redo(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        self.history.ignore = true;\n\n        if let Some(changes) = self.history.redo() {\n            for change in changes {\n                if let Some(index) = self\n                    .panels\n                    .iter()\n                    .position(|item| item.panel.view().entity_id() == change.tile_id)\n                {\n                    if let Some(new_bounds) = change.new_bounds {\n                        self.panels[index].bounds = new_bounds;\n                    }\n                    if let Some(new_order) = change.new_order {\n                        let item = self.panels.remove(index);\n                        self.panels.insert(new_order, item);\n                    }\n                }\n            }\n            cx.emit(PanelEvent::LayoutChanged);\n        }\n\n        self.history.ignore = false;\n        cx.notify();\n    }\n\n    /// Returns the active panel, if any.\n    pub fn active_panel(&self, cx: &App) -> Option<Arc<dyn PanelView>> {\n        self.panels.last().and_then(|item| {\n            if let Ok(tab_panel) = item.panel.view().downcast::<TabPanel>() {\n                tab_panel.read(cx).active_panel(cx)\n            } else if let Ok(_) = item.panel.view().downcast::<StackPanel>() {\n                None\n            } else {\n                Some(item.panel.clone())\n            }\n        })\n    }\n\n    /// Produce a vector of AnyElement representing the three possible resize handles\n    fn render_resize_handles(\n        &mut self,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n        entity_id: EntityId,\n        item: &TileItem,\n    ) -> Vec<AnyElement> {\n        let item_id = item.id;\n        let item_bounds = item.bounds;\n        let handle_offset = -HANDLE_SIZE + px(1.);\n\n        let mut elements = Vec::new();\n\n        // Left resize handle\n        elements.push(\n            div()\n                .id(\"left-resize-handle\")\n                .cursor_ew_resize()\n                .absolute()\n                .top_0()\n                .left(handle_offset)\n                .w(HANDLE_SIZE)\n                .h(item_bounds.size.height)\n                .on_mouse_down(\n                    MouseButton::Left,\n                    cx.listener({\n                        move |this, event: &MouseDownEvent, window, cx| {\n                            this.on_resize_handle_mouse_down(\n                                ResizeSide::Left,\n                                item_id,\n                                item_bounds,\n                                event,\n                                window,\n                                cx,\n                            );\n                        }\n                    }),\n                )\n                .on_drag(DragResizing(entity_id), |drag, _, _, cx| {\n                    cx.stop_propagation();\n                    cx.new(|_| drag.clone())\n                })\n                .on_drag_move(cx.listener(\n                    move |this, e: &DragMoveEvent<DragResizing>, window, cx| match e.drag(cx) {\n                        DragResizing(id) => {\n                            if *id != entity_id {\n                                return;\n                            }\n\n                            let Some(ref drag_data) = this.resizing_drag_data else {\n                                return;\n                            };\n                            if drag_data.side != ResizeSide::Left {\n                                return;\n                            }\n\n                            let pos = e.event.position;\n                            let delta = drag_data.last_position.x - pos.x;\n                            let new_x = (drag_data.last_bounds.origin.x - delta).max(px(0.0));\n                            let size_delta = drag_data.last_bounds.origin.x - new_x;\n                            let new_width = (drag_data.last_bounds.size.width + size_delta)\n                                .max(MINIMUM_SIZE.width);\n                            this.resize(Some(new_x), None, Some(new_width), None, window, cx);\n                        }\n                    },\n                ))\n                .into_any_element(),\n        );\n\n        // Right resize handle\n        elements.push(\n            div()\n                .id(\"right-resize-handle\")\n                .cursor_ew_resize()\n                .absolute()\n                .top_0()\n                .right(handle_offset)\n                .w(HANDLE_SIZE)\n                .h(item_bounds.size.height)\n                .on_mouse_down(\n                    MouseButton::Left,\n                    cx.listener({\n                        move |this, event: &MouseDownEvent, window, cx| {\n                            this.on_resize_handle_mouse_down(\n                                ResizeSide::Right,\n                                item_id,\n                                item_bounds,\n                                event,\n                                window,\n                                cx,\n                            );\n                        }\n                    }),\n                )\n                .on_drag(DragResizing(entity_id), |drag, _, _, cx| {\n                    cx.stop_propagation();\n                    cx.new(|_| drag.clone())\n                })\n                .on_drag_move(cx.listener(\n                    move |this, e: &DragMoveEvent<DragResizing>, window, cx| match e.drag(cx) {\n                        DragResizing(id) => {\n                            if *id != entity_id {\n                                return;\n                            }\n\n                            let Some(ref drag_data) = this.resizing_drag_data else {\n                                return;\n                            };\n\n                            if drag_data.side != ResizeSide::Right {\n                                return;\n                            }\n\n                            let pos = e.event.position;\n                            let delta = pos.x - drag_data.last_position.x;\n                            let new_width =\n                                (drag_data.last_bounds.size.width + delta).max(MINIMUM_SIZE.width);\n                            this.resize(None, None, Some(new_width), None, window, cx);\n                        }\n                    },\n                ))\n                .into_any_element(),\n        );\n\n        // Top resize handle\n        elements.push(\n            div()\n                .id(\"top-resize-handle\")\n                .cursor_ns_resize()\n                .absolute()\n                .left(px(0.0))\n                .top(handle_offset)\n                .w(item_bounds.size.width)\n                .h(HANDLE_SIZE)\n                .on_mouse_down(\n                    MouseButton::Left,\n                    cx.listener({\n                        move |this, event: &MouseDownEvent, window, cx| {\n                            this.on_resize_handle_mouse_down(\n                                ResizeSide::Top,\n                                item_id,\n                                item_bounds,\n                                event,\n                                window,\n                                cx,\n                            );\n                        }\n                    }),\n                )\n                .on_drag(DragResizing(entity_id), |drag, _, _, cx| {\n                    cx.stop_propagation();\n                    cx.new(|_| drag.clone())\n                })\n                .on_drag_move(cx.listener(\n                    move |this, e: &DragMoveEvent<DragResizing>, window, cx| match e.drag(cx) {\n                        DragResizing(id) => {\n                            if *id != entity_id {\n                                return;\n                            }\n\n                            let Some(ref drag_data) = this.resizing_drag_data else {\n                                return;\n                            };\n                            if drag_data.side != ResizeSide::Top {\n                                return;\n                            }\n\n                            let pos = e.event.position;\n                            let delta = drag_data.last_position.y - pos.y;\n                            let new_y = (drag_data.last_bounds.origin.y - delta).max(px(0.));\n                            let size_delta = drag_data.last_position.y - new_y;\n                            let new_height = (drag_data.last_bounds.size.height + size_delta)\n                                .max(MINIMUM_SIZE.width);\n                            this.resize(None, Some(new_y), None, Some(new_height), window, cx);\n                        }\n                    },\n                ))\n                .into_any_element(),\n        );\n\n        // Bottom resize handle\n        elements.push(\n            div()\n                .id(\"bottom-resize-handle\")\n                .cursor_ns_resize()\n                .absolute()\n                .left(px(0.0))\n                .bottom(handle_offset)\n                .w(item_bounds.size.width)\n                .h(HANDLE_SIZE)\n                .on_mouse_down(\n                    MouseButton::Left,\n                    cx.listener({\n                        move |this, event: &MouseDownEvent, window, cx| {\n                            this.on_resize_handle_mouse_down(\n                                ResizeSide::Bottom,\n                                item_id,\n                                item_bounds,\n                                event,\n                                window,\n                                cx,\n                            );\n                        }\n                    }),\n                )\n                .on_drag(DragResizing(entity_id), |drag, _, _, cx| {\n                    cx.stop_propagation();\n                    cx.new(|_| drag.clone())\n                })\n                .on_drag_move(cx.listener(\n                    move |this, e: &DragMoveEvent<DragResizing>, window, cx| match e.drag(cx) {\n                        DragResizing(id) => {\n                            if *id != entity_id {\n                                return;\n                            }\n\n                            let Some(ref drag_data) = this.resizing_drag_data else {\n                                return;\n                            };\n\n                            if drag_data.side != ResizeSide::Bottom {\n                                return;\n                            }\n\n                            let pos = e.event.position;\n                            let delta = pos.y - drag_data.last_position.y;\n                            let new_height =\n                                (drag_data.last_bounds.size.height + delta).max(MINIMUM_SIZE.width);\n                            this.resize(None, None, None, Some(new_height), window, cx);\n                        }\n                    },\n                ))\n                .into_any_element(),\n        );\n\n        // Corner resize handle\n        elements.push(\n            div()\n                .child(\n                    Icon::new(IconName::ResizeCorner)\n                        .size_3()\n                        .absolute()\n                        .right(px(1.))\n                        .bottom(px(1.))\n                        .text_color(cx.theme().muted_foreground.opacity(0.5)),\n                )\n                .child(\n                    div()\n                        .id(\"corner-resize-handle\")\n                        .cursor_nwse_resize()\n                        .absolute()\n                        .right(handle_offset)\n                        .bottom(handle_offset)\n                        .size_3()\n                        .on_mouse_down(\n                            MouseButton::Left,\n                            cx.listener({\n                                move |this, event: &MouseDownEvent, window, cx| {\n                                    this.on_resize_handle_mouse_down(\n                                        ResizeSide::BottomRight,\n                                        item_id,\n                                        item_bounds,\n                                        event,\n                                        window,\n                                        cx,\n                                    );\n                                }\n                            }),\n                        )\n                        .on_drag(DragResizing(entity_id), |drag, _, _, cx| {\n                            cx.stop_propagation();\n                            cx.new(|_| drag.clone())\n                        })\n                        .on_drag_move(cx.listener(\n                            move |this, e: &DragMoveEvent<DragResizing>, window, cx| {\n                                match e.drag(cx) {\n                                    DragResizing(id) => {\n                                        if *id != entity_id {\n                                            return;\n                                        }\n\n                                        let Some(ref drag_data) = this.resizing_drag_data else {\n                                            return;\n                                        };\n\n                                        if drag_data.side != ResizeSide::BottomRight {\n                                            return;\n                                        }\n\n                                        let pos = e.event.position;\n                                        let delta_x = pos.x - drag_data.last_position.x;\n                                        let delta_y = pos.y - drag_data.last_position.y;\n                                        let new_width = (drag_data.last_bounds.size.width\n                                            + delta_x)\n                                            .max(MINIMUM_SIZE.width);\n                                        let new_height = (drag_data.last_bounds.size.height\n                                            + delta_y)\n                                            .max(MINIMUM_SIZE.height);\n                                        this.resize(\n                                            None,\n                                            None,\n                                            Some(new_width),\n                                            Some(new_height),\n                                            window,\n                                            cx,\n                                        );\n                                    }\n                                }\n                            },\n                        )),\n                )\n                .into_any_element(),\n        );\n\n        elements\n    }\n\n    fn on_resize_handle_mouse_down(\n        &mut self,\n        side: ResizeSide,\n        item_id: EntityId,\n        item_bounds: Bounds<Pixels>,\n        event: &MouseDownEvent,\n        _: &mut Window,\n        cx: &mut Context<'_, Self>,\n    ) {\n        let last_position = event.position;\n        self.resizing_id = Some(item_id);\n        self.resizing_drag_data = Some(ResizeDrag {\n            side,\n            last_position,\n            last_bounds: item_bounds,\n        });\n\n        if let Some(new_id) = self.bring_to_front(self.resizing_id, cx) {\n            self.resizing_id = Some(new_id);\n        }\n        cx.stop_propagation();\n    }\n\n    /// Produce the drag-bar element for the given panel item\n    fn render_drag_bar(\n        &mut self,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n        entity_id: EntityId,\n        item: &TileItem,\n    ) -> AnyElement {\n        let item_id = item.id;\n        let item_bounds = item.bounds;\n\n        h_flex()\n            .id(\"drag-bar\")\n            .absolute()\n            .w_full()\n            .h(DRAG_BAR_HEIGHT)\n            .bg(cx.theme().transparent)\n            .on_mouse_down(\n                MouseButton::Left,\n                cx.listener(move |this, event: &MouseDownEvent, _, cx| {\n                    let inner_pos = event.position - this.bounds.origin;\n                    this.dragging_id = Some(item_id);\n                    this.dragging_initial_mouse = inner_pos;\n                    this.dragging_initial_bounds = item_bounds;\n\n                    if let Some(new_id) = this.bring_to_front(Some(item_id), cx) {\n                        this.dragging_id = Some(new_id);\n                    }\n                }),\n            )\n            .on_drag(DragMoving(entity_id), |drag, _, _, cx| {\n                cx.stop_propagation();\n                cx.new(|_| drag.clone())\n            })\n            .on_drag_move(\n                cx.listener(\n                    move |this, e: &DragMoveEvent<DragMoving>, _, cx| match e.drag(cx) {\n                        DragMoving(id) => {\n                            if *id != entity_id {\n                                return;\n                            }\n                            this.update_position(e.event.position, cx);\n                        }\n                    },\n                ),\n            )\n            .into_any_element()\n    }\n\n    fn render_panel(\n        &mut self,\n        item: &TileItem,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Div {\n        let entity_id = cx.entity_id();\n        let item_id = item.id;\n        let panel_view = item.panel.view();\n\n        v_flex()\n            .occlude()\n            .bg(cx.theme().background)\n            .border_1()\n            .border_color(cx.theme().border)\n            .absolute()\n            .left(item.bounds.origin.x)\n            .top(item.bounds.origin.y)\n            // More 1px to account for the border width when 2 panels are too close\n            .w(item.bounds.size.width + px(1.))\n            .h(item.bounds.size.height + px(1.))\n            .rounded(cx.theme().tile_radius)\n            .child(h_flex().overflow_hidden().size_full().child(panel_view))\n            .children(self.render_resize_handles(window, cx, entity_id, &item))\n            .child(self.render_drag_bar(window, cx, entity_id, &item))\n            .on_mouse_down(\n                MouseButton::Left,\n                cx.listener(move |this, _, _, _| {\n                    this.dragging_id = Some(item_id);\n                }),\n            )\n            // Here must be mouse up for avoid conflict with Drag event\n            .on_mouse_up(\n                MouseButton::Left,\n                cx.listener(move |this, _, _, cx| {\n                    if this.dragging_id == Some(item_id) {\n                        this.dragging_id = None;\n                        this.bring_to_front(Some(item_id), cx);\n                    }\n                }),\n            )\n    }\n\n    /// Handle the mouse up event to finalize drag or resize operations\n    fn on_mouse_up(&mut self, _: &mut Window, cx: &mut Context<'_, Tiles>) {\n        // Check if a drag or resize was active\n        if self.dragging_id.is_some()\n            || self.resizing_id.is_some()\n            || self.resizing_drag_data.is_some()\n        {\n            let mut changes_to_push = vec![];\n\n            // Handle dragging\n            if let Some(dragging_id) = self.dragging_id {\n                if let Some(idx) = self.panels.iter().position(|p| p.id == dragging_id) {\n                    let initial_bounds = self.dragging_initial_bounds;\n                    let current_bounds = self.panels[idx].bounds;\n\n                    // Apply grid alignment to final position\n                    let aligned_origin = round_point_to_nearest_ten(current_bounds.origin, cx);\n\n                    if initial_bounds.origin != aligned_origin\n                        || initial_bounds.size != current_bounds.size\n                    {\n                        self.panels[idx].bounds.origin = aligned_origin;\n\n                        changes_to_push.push(TileChange {\n                            tile_id: self.panels[idx].panel.view().entity_id(),\n                            old_bounds: Some(initial_bounds),\n                            new_bounds: Some(self.panels[idx].bounds),\n                            old_order: None,\n                            new_order: None,\n                            version: 0,\n                        });\n                    }\n                }\n            }\n\n            // Handle resizing\n            if let Some(resizing_id) = self.resizing_id {\n                if let Some(drag_data) = &self.resizing_drag_data {\n                    if let Some(item) = self.panel(&resizing_id) {\n                        let initial_bounds = drag_data.last_bounds;\n                        let current_bounds = item.bounds;\n                        if initial_bounds.size != current_bounds.size {\n                            changes_to_push.push(TileChange {\n                                tile_id: item.panel.view().entity_id(),\n                                old_bounds: Some(initial_bounds),\n                                new_bounds: Some(current_bounds),\n                                old_order: None,\n                                new_order: None,\n                                version: 0,\n                            });\n                        }\n                    }\n                }\n            }\n\n            // Push changes to history if any\n            if !changes_to_push.is_empty() {\n                for change in changes_to_push {\n                    self.history.push(change);\n                }\n            }\n\n            // Reset drag and resize state\n            self.reset_current_index();\n            self.resizing_drag_data = None;\n            cx.emit(PanelEvent::LayoutChanged);\n            cx.notify();\n        }\n    }\n}\n\n#[inline]\nfn round_to_nearest_ten(value: Pixels, cx: &App) -> Pixels {\n    (value / cx.theme().tile_grid_size).round() * cx.theme().tile_grid_size\n}\n\n#[inline]\nfn round_point_to_nearest_ten(point: Point<Pixels>, cx: &App) -> Point<Pixels> {\n    Point::new(\n        round_to_nearest_ten(point.x, cx),\n        round_to_nearest_ten(point.y, cx),\n    )\n}\n\nimpl Focusable for Tiles {\n    fn focus_handle(&self, _cx: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\nimpl EventEmitter<PanelEvent> for Tiles {}\nimpl EventEmitter<DismissEvent> for Tiles {}\nimpl Render for Tiles {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let view = cx.entity().clone();\n        let panels = self.sorted_panels();\n        let scroll_bounds =\n            self.panels\n                .iter()\n                .fold(Bounds::default(), |acc: Bounds<Pixels>, item| Bounds {\n                    origin: Point {\n                        x: acc.origin.x.min(item.bounds.origin.x),\n                        y: acc.origin.y.min(item.bounds.origin.y),\n                    },\n                    size: Size {\n                        width: acc.size.width.max(item.bounds.right()),\n                        height: acc.size.height.max(item.bounds.bottom()),\n                    },\n                });\n        let scroll_size = scroll_bounds.size - size(scroll_bounds.origin.x, scroll_bounds.origin.y);\n\n        div()\n            .relative()\n            .bg(cx.theme().tiles)\n            .child(\n                div()\n                    .id(\"tiles\")\n                    .track_scroll(&self.scroll_handle)\n                    .size_full()\n                    .top(-px(1.))\n                    .left(-px(1.))\n                    .overflow_scroll()\n                    .children(\n                        panels\n                            .into_iter()\n                            .map(|item| self.render_panel(&item, window, cx)),\n                    )\n                    .on_prepaint(move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds))\n                    .on_drop(cx.listener(move |_, item: &AnyDrag, _, cx| {\n                        cx.emit(DragDrop(item.clone()));\n                    })),\n            )\n            .on_mouse_up(\n                MouseButton::Left,\n                cx.listener(move |this, _event: &MouseUpEvent, window, cx| {\n                    this.on_mouse_up(window, cx);\n                }),\n            )\n            .child(\n                div()\n                    .absolute()\n                    .top_0()\n                    .left_0()\n                    .right_0()\n                    .bottom_0()\n                    .child(\n                        Scrollbar::new(&self.scroll_handle)\n                            .scroll_size(scroll_size)\n                            .when_some(self.scrollbar_show, |this, scrollbar_show| {\n                                this.scrollbar_show(scrollbar_show)\n                            }),\n                    ),\n            )\n            .size_full()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/element_ext.rs",
    "content": "use gpui::{\n    AnyElement, App, Bounds, IntoElement, ParentElement, Pixels, Styled as _, Window, canvas,\n};\n\nuse crate::{Sizable, Size};\n\n#[derive(Default)]\nstruct ChildElementOptions {\n    ix: usize,\n    size: Size,\n}\n\n#[allow(patterns_in_fns_without_body)]\npub trait ChildElement: Sizable + IntoElement {\n    fn with_ix(mut self, ix: usize) -> Self;\n}\n\n/// A type-erased element that can accept a [`AnyChildElementOptions`] before being rendered.\npub struct AnyChildElement(Box<dyn FnOnce(ChildElementOptions) -> AnyElement>);\n\nimpl AnyChildElement {\n    pub fn new(element: impl ChildElement + 'static) -> Self {\n        Self(Box::new(|options| {\n            element.with_ix(options.ix).with_size(options.size).into_any_element()\n        }))\n    }\n\n    pub fn into_any(self, ix: usize, size: Size) -> AnyElement {\n        (self.0)(ChildElementOptions { ix, size })\n    }\n}\n\n/// A trait to extend [`gpui::Element`] with additional functionality.\npub trait ElementExt: ParentElement + Sized {\n    /// Add a prepaint callback to the element.\n    ///\n    /// This is a helper method to get the bounds of the element after paint.\n    ///\n    /// The first argument is the bounds of the element in pixels.\n    ///\n    /// See also [`gpui::canvas`].\n    fn on_prepaint<F>(self, f: F) -> Self\n    where\n        F: FnOnce(Bounds<Pixels>, &mut Window, &mut App) + 'static,\n    {\n        self.child(\n            canvas(move |bounds, window, cx| f(bounds, window, cx), |_, _, _, _| {})\n                .absolute()\n                .size_full(),\n        )\n    }\n}\n\nimpl<T: ParentElement> ElementExt for T {}\n"
  },
  {
    "path": "crates/ui/src/event.rs",
    "content": "use gpui::{App, ClickEvent, InteractiveElement, Stateful, Window};\n\npub trait InteractiveElementExt: InteractiveElement {\n    /// Set the listener for a double click event.\n    fn on_double_click(\n        mut self,\n        listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self\n    where\n        Self: Sized,\n    {\n        self.interactivity().on_click(move |event, window, cx| {\n            if event.click_count() == 2 {\n                listener(event, window, cx);\n            }\n        });\n        self\n    }\n}\n\nimpl<E: InteractiveElement> InteractiveElementExt for Stateful<E> {}\n"
  },
  {
    "path": "crates/ui/src/fixtures/layout.json",
    "content": "{\n  \"center\": {\n    \"panel_name\": \"StackPanel\",\n    \"children\": [\n      {\n        \"panel_name\": \"TabPanel\",\n        \"children\": [\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"ButtonStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"InputStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"TextStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"SelectStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"DialogStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"SwitchStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"ProgressStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"DataTableStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"ImageStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"IconStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"TooltipStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"ProgressStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"CalendarStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"ResizableStory\"\n              }\n            }\n          },\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"ScrollbarStory\"\n              }\n            }\n          }\n        ],\n        \"info\": {\n          \"tabs\": {\n            \"active_index\": 0\n          }\n        }\n      },\n      {\n        \"panel_name\": \"TabPanel\",\n        \"children\": [\n          {\n            \"panel_name\": \"StoryContainer\",\n            \"children\": [],\n            \"info\": {\n              \"panel\": {\n                \"story_klass\": \"PopupStory\"\n              }\n            }\n          }\n        ],\n        \"info\": {\n          \"tabs\": {\n            \"active_index\": 0\n          }\n        }\n      }\n    ],\n    \"info\": {\n      \"stack\": {\n        \"sizes\": [704.0, 263.0],\n        \"axis\": 1\n      }\n    }\n  },\n  \"left_dock\": {\n    \"panel\": {\n      \"panel_name\": \"TabPanel\",\n      \"children\": [\n        {\n          \"panel_name\": \"StoryContainer\",\n          \"children\": [],\n          \"info\": {\n            \"panel\": {\n              \"story_klass\": \"ListStory\"\n            }\n          }\n        }\n      ],\n      \"info\": {\n        \"tabs\": {\n          \"active_index\": 0\n        }\n      }\n    },\n    \"placement\": \"left\",\n    \"size\": 350.0,\n    \"open\": true,\n    \"resizeable\": true\n  },\n  \"right_dock\": {\n    \"panel\": {\n      \"panel_name\": \"TabPanel\",\n      \"children\": [\n        {\n          \"panel_name\": \"StoryContainer\",\n          \"children\": [],\n          \"info\": {\n            \"panel\": {\n              \"story_klass\": \"ImageStory\"\n            }\n          }\n        }\n      ],\n      \"info\": {\n        \"tabs\": {\n          \"active_index\": 0\n        }\n      }\n    },\n    \"placement\": \"right\",\n    \"size\": 320.0,\n    \"open\": true,\n    \"resizeable\": true\n  },\n  \"bottom_dock\": {\n    \"panel\": {\n      \"panel_name\": \"TabPanel\",\n      \"children\": [\n        {\n          \"panel_name\": \"StoryContainer\",\n          \"children\": [],\n          \"info\": {\n            \"panel\": {\n              \"story_klass\": \"TextStory\"\n            }\n          }\n        },\n        {\n          \"panel_name\": \"StoryContainer\",\n          \"children\": [],\n          \"info\": {\n            \"panel\": {\n              \"story_klass\": \"IconStory\"\n            }\n          }\n        }\n      ],\n      \"info\": {\n        \"tabs\": {\n          \"active_index\": 0\n        }\n      }\n    },\n    \"placement\": \"bottom\",\n    \"size\": 200.0,\n    \"open\": true,\n    \"resizeable\": true\n  }\n}\n"
  },
  {
    "path": "crates/ui/src/focus_trap.rs",
    "content": "use gpui::{\n    AnyElement, App, Bounds, Element, ElementId, FocusHandle, Global, GlobalElementId,\n    InteractiveElement, Interactivity, IntoElement, LayoutId, ParentElement, Pixels,\n    StatefulInteractiveElement, StyleRefinement, Styled, WeakFocusHandle, Window,\n};\nuse std::collections::HashMap;\n\n/// Initialize the focus trap manager as a global\npub(crate) fn init(cx: &mut App) {\n    cx.set_global(FocusTrapManager::new());\n}\n\n/// An extension trait to add `focus_trap` functionality to interactive elements.\npub trait FocusTrapElement: InteractiveElement + Sized {\n    /// Enable focus trap for this element.\n    ///\n    /// When enabled, focus will automatically cycle within this container\n    /// instead of escaping to parent elements. This is useful for modal dialogs,\n    /// sheets, and other overlay components.\n    ///\n    /// The focus trap works by:\n    /// 1. Registering this element as a focus trap container\n    /// 2. When Tab/Shift-Tab is pressed, Root intercepts the event\n    /// 3. If focus would leave the container, it cycles back to the beginning/end\n    ///\n    /// # Example\n    ///\n    /// ```ignore\n    /// v_flex()\n    ///     .child(Button::new(\"btn1\").label(\"Button 1\"))\n    ///     .child(Button::new(\"btn2\").label(\"Button 2\"))\n    ///     .child(Button::new(\"btn3\").label(\"Button 3\"))\n    ///     .focus_trap(\"trap1\", &self.container_focus_handle)\n    /// // Pressing Tab will cycle: btn1 -> btn2 -> btn3 -> btn1\n    /// // Focus will not escape to elements outside this container\n    /// ```\n    ///\n    /// See also: <https://github.com/focus-trap/focus-trap-react>\n    fn focus_trap(\n        self,\n        id: impl Into<ElementId>,\n        focus_handle: &FocusHandle,\n    ) -> FocusTrapContainer<Self>\n    where\n        Self: ParentElement + Styled + Element + 'static,\n    {\n        FocusTrapContainer::new(id, focus_handle.clone(), self)\n    }\n}\nimpl<T: InteractiveElement + Sized> FocusTrapElement for T {}\n\n/// Global state to manage all focus trap containers\npub(crate) struct FocusTrapManager {\n    /// Map from container element ID to its focus trap info\n    traps: HashMap<GlobalElementId, WeakFocusHandle>,\n}\n\nimpl Global for FocusTrapManager {}\n\nimpl FocusTrapManager {\n    /// Create a new focus trap manager\n    fn new() -> Self {\n        Self {\n            traps: HashMap::new(),\n        }\n    }\n\n    pub(crate) fn global(cx: &App) -> &Self {\n        cx.global::<FocusTrapManager>()\n    }\n\n    fn global_mut(cx: &mut App) -> &mut Self {\n        cx.global_mut::<FocusTrapManager>()\n    }\n\n    /// Register a focus trap container\n    fn register_trap(id: &GlobalElementId, container_handle: WeakFocusHandle, cx: &mut App) {\n        let this = Self::global_mut(cx);\n        this.traps.insert(id.clone(), container_handle);\n        this.cleanup();\n    }\n\n    /// Find which focus trap contains the currently focused element\n    pub(crate) fn find_active_trap(window: &Window, cx: &App) -> Option<FocusHandle> {\n        for (_id, container_handle) in Self::global(cx).traps.iter() {\n            let Some(container) = container_handle.upgrade() else {\n                continue;\n            };\n\n            if container.contains_focused(window, cx) {\n                return Some(container.clone());\n            }\n        }\n        None\n    }\n\n    /// Cleanup any traps with dropped handles\n    fn cleanup(&mut self) {\n        self.traps.retain(|_, handle| handle.upgrade().is_some());\n    }\n}\n\nimpl Default for FocusTrapManager {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n/// A wrapper element that implements focus trap behavior.\n///\n/// This element wraps another element and registers it as a focus trap container.\n/// Focus will automatically cycle within the container when Tab/Shift-Tab is pressed.\npub struct FocusTrapContainer<E: InteractiveElement + ParentElement + Styled + Element> {\n    id: ElementId,\n    focus_handle: FocusHandle,\n    base: E,\n}\n\nimpl<E: InteractiveElement + ParentElement + Styled + Element> FocusTrapContainer<E> {\n    pub(crate) fn new(id: impl Into<ElementId>, focus_handle: FocusHandle, child: E) -> Self {\n        Self {\n            id: id.into(),\n            base: child.track_focus(&focus_handle),\n            focus_handle,\n        }\n    }\n}\n\nimpl<E: InteractiveElement + ParentElement + Styled + Element> IntoElement\n    for FocusTrapContainer<E>\n{\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\nimpl<E: InteractiveElement + ParentElement + Styled + Element> ParentElement\n    for FocusTrapContainer<E>\n{\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.base.extend(elements);\n    }\n}\nimpl<E: InteractiveElement + ParentElement + Styled + Element> InteractiveElement\n    for FocusTrapContainer<E>\n{\n    fn interactivity(&mut self) -> &mut Interactivity {\n        self.base.interactivity()\n    }\n}\nimpl<E: InteractiveElement + ParentElement + Styled + Element> StatefulInteractiveElement\n    for FocusTrapContainer<E>\n{\n}\nimpl<E: InteractiveElement + ParentElement + Styled + Element> Styled for FocusTrapContainer<E> {\n    fn style(&mut self) -> &mut StyleRefinement {\n        self.base.style()\n    }\n}\n\nimpl<E: InteractiveElement + ParentElement + Styled + Element + 'static> Element\n    for FocusTrapContainer<E>\n{\n    type RequestLayoutState = E::RequestLayoutState;\n    type PrepaintState = E::PrepaintState;\n\n    fn id(&self) -> Option<ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        global_id: Option<&gpui::GlobalElementId>,\n        _inspector_id: Option<&gpui::InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (LayoutId, Self::RequestLayoutState) {\n        // Register this focus trap with the manager\n        FocusTrapManager::register_trap(global_id.unwrap(), self.focus_handle.downgrade(), cx);\n\n        self.base.request_layout(global_id, None, window, cx)\n    }\n\n    fn prepaint(\n        &mut self,\n        global_id: Option<&gpui::GlobalElementId>,\n        inspector_id: Option<&gpui::InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        request_layout: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::PrepaintState {\n        self.base\n            .prepaint(global_id, inspector_id, bounds, request_layout, window, cx)\n    }\n\n    fn paint(\n        &mut self,\n        global_id: Option<&gpui::GlobalElementId>,\n        inspector_id: Option<&gpui::InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        request_layout: &mut Self::RequestLayoutState,\n        prepaint: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        self.base.paint(\n            global_id,\n            inspector_id,\n            bounds,\n            request_layout,\n            prepaint,\n            window,\n            cx,\n        )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/form/field.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{\n    AlignItems, AnyElement, AnyView, App, Axis, Div, Element, ElementId, InteractiveElement as _,\n    IntoElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, StyleRefinement, Styled,\n    Window, div, prelude::FluentBuilder as _, px,\n};\n\nuse crate::{ActiveTheme as _, AxisExt, Size, StyledExt, h_flex, v_flex};\n\n#[derive(Clone, Copy)]\npub(super) struct FieldProps {\n    pub(super) size: Size,\n    pub(super) layout: Axis,\n    pub(super) columns: usize,\n\n    pub(super) label_width: Option<Pixels>,\n    pub(super) label_text_size: Option<Rems>,\n}\n\nimpl Default for FieldProps {\n    fn default() -> Self {\n        Self {\n            layout: Axis::Vertical,\n            size: Size::default(),\n            columns: 1,\n            label_width: Some(px(140.)),\n            label_text_size: None,\n        }\n    }\n}\n\npub enum FieldBuilder {\n    String(SharedString),\n    Element(Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>),\n    View(AnyView),\n}\n\nimpl Default for FieldBuilder {\n    fn default() -> Self {\n        Self::String(SharedString::default())\n    }\n}\n\nimpl From<AnyView> for FieldBuilder {\n    fn from(view: AnyView) -> Self {\n        Self::View(view)\n    }\n}\n\nimpl RenderOnce for FieldBuilder {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        match self {\n            FieldBuilder::String(value) => value.into_any_element(),\n            FieldBuilder::Element(builder) => builder(window, cx),\n            FieldBuilder::View(view) => view.into_any(),\n        }\n    }\n}\n\nimpl From<&'static str> for FieldBuilder {\n    fn from(value: &'static str) -> Self {\n        Self::String(value.into())\n    }\n}\n\nimpl From<String> for FieldBuilder {\n    fn from(value: String) -> Self {\n        Self::String(value.into())\n    }\n}\n\nimpl From<SharedString> for FieldBuilder {\n    fn from(value: SharedString) -> Self {\n        Self::String(value)\n    }\n}\n\n/// Form field element.\n#[derive(IntoElement)]\npub struct Field {\n    id: ElementId,\n    props: FieldProps,\n    style: StyleRefinement,\n    label: Option<FieldBuilder>,\n    label_indent: bool,\n    description: Option<FieldBuilder>,\n    /// Used to render the actual form field, e.g.: Input, Switch...\n    children: Vec<AnyElement>,\n    visible: bool,\n    required: bool,\n    /// Alignment of the form field.\n    align_items: Option<AlignItems>,\n    col_span: u16,\n    col_start: Option<i16>,\n    col_end: Option<i16>,\n}\n\nimpl Field {\n    pub fn new() -> Self {\n        Self {\n            id: 0.into(),\n            props: FieldProps::default(),\n            style: StyleRefinement::default(),\n            label: None,\n            description: None,\n            children: Vec::new(),\n            visible: true,\n            required: false,\n            label_indent: true,\n            align_items: None,\n            col_span: 1,\n            col_start: None,\n            col_end: None,\n        }\n    }\n\n    /// Sets the label for the form field.\n    pub fn label(mut self, label: impl Into<FieldBuilder>) -> Self {\n        self.label = Some(label.into());\n        self\n    }\n\n    /// Sets indent with the label width (in Horizontal layout), default is `true`.\n    ///\n    /// Sometimes you want to align the input form left (Default is align after the label width in Horizontal layout).\n    ///\n    /// This is only work when the `label` is not set.\n    pub fn label_indent(mut self, indent: bool) -> Self {\n        self.label_indent = indent;\n        self\n    }\n\n    /// Sets the label for the form field using a function.\n    pub fn label_fn<F, E>(mut self, label: F) -> Self\n    where\n        E: IntoElement,\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n    {\n        self.label = Some(FieldBuilder::Element(Rc::new(move |window, cx| {\n            label(window, cx).into_any_element()\n        })));\n        self\n    }\n\n    /// Sets the description for the form field.\n    pub fn description(mut self, description: impl Into<FieldBuilder>) -> Self {\n        self.description = Some(description.into());\n        self\n    }\n\n    /// Sets the description for the form field using a function.\n    pub fn description_fn<F, E>(mut self, description: F) -> Self\n    where\n        E: IntoElement,\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n    {\n        self.description = Some(FieldBuilder::Element(Rc::new(move |window, cx| {\n            description(window, cx).into_any_element()\n        })));\n        self\n    }\n\n    /// Set the visibility of the form field, default is `true`.\n    pub fn visible(mut self, visible: bool) -> Self {\n        self.visible = visible;\n        self\n    }\n\n    /// Set the required status of the form field, default is `false`.\n    pub fn required(mut self, required: bool) -> Self {\n        self.required = required;\n        self\n    }\n\n    /// Set the properties for the form field.\n    ///\n    /// This is internal API for sync props from From.\n    pub(super) fn props(mut self, ix: usize, props: FieldProps) -> Self {\n        self.id = ix.into();\n        self.props = props;\n        self\n    }\n\n    /// Align the form field items to the start, this is the default.\n    pub fn items_start(mut self) -> Self {\n        self.align_items = Some(AlignItems::Start);\n        self\n    }\n\n    /// Align the form field items to the end.\n    pub fn items_end(mut self) -> Self {\n        self.align_items = Some(AlignItems::End);\n        self\n    }\n\n    /// Align the form field items to the center.\n    pub fn items_center(mut self) -> Self {\n        self.align_items = Some(AlignItems::Center);\n        self\n    }\n\n    /// Sets the column span for the form field.\n    ///\n    /// Default is 1.\n    pub fn col_span(mut self, col_span: u16) -> Self {\n        self.col_span = col_span;\n        self\n    }\n\n    /// Sets the column start of this form field.\n    pub fn col_start(mut self, col_start: i16) -> Self {\n        self.col_start = Some(col_start);\n        self\n    }\n\n    /// Sets the column end of this form field.\n    pub fn col_end(mut self, col_end: i16) -> Self {\n        self.col_end = Some(col_end);\n        self\n    }\n}\n\nimpl ParentElement for Field {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Styled for Field {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Field {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let layout = self.props.layout;\n\n        let label_width = if layout.is_vertical() {\n            None\n        } else {\n            self.props.label_width\n        };\n        let has_label = self.label_indent;\n\n        #[inline]\n        fn wrap_div(layout: Axis) -> Div {\n            if layout.is_vertical() {\n                v_flex()\n            } else {\n                h_flex()\n            }\n        }\n\n        #[inline]\n        fn wrap_label(label_width: Option<Pixels>) -> Div {\n            div().when_some(label_width, |this, width| this.w(width).flex_shrink_0())\n        }\n\n        let gap = match self.props.size {\n            Size::Large => px(8.),\n            Size::XSmall | Size::Small => px(4.),\n            _ => px(4.),\n        };\n        let inner_gap = if layout.is_horizontal() {\n            gap\n        } else {\n            gap / 2.\n        };\n\n        v_flex()\n            .flex_1()\n            .gap(gap / 2.)\n            .col_span(self.col_span)\n            .when_some(self.col_start, |this, start| this.col_start(start))\n            .when_some(self.col_end, |this, end| this.col_end(end))\n            .refine_style(&self.style)\n            .child(\n                // This warp for aligning the Label + Input\n                wrap_div(layout)\n                    .id(self.id)\n                    .gap(inner_gap)\n                    .when_some(self.align_items, |this, align| {\n                        this.map(|this| match align {\n                            AlignItems::Start => this.items_start(),\n                            AlignItems::End => this.items_end(),\n                            AlignItems::Center => this.items_center(),\n                            AlignItems::Baseline => this.items_baseline(),\n                            _ => this,\n                        })\n                    })\n                    .when(has_label, |this| {\n                        // Label\n                        this.child(\n                            wrap_label(label_width)\n                                .text_sm()\n                                .when_some(self.props.label_text_size, |this, size| {\n                                    this.text_size(size)\n                                })\n                                .font_medium()\n                                .gap_1()\n                                .items_center()\n                                .when_some(self.label, |this, builder| {\n                                    this.child(\n                                        h_flex()\n                                            .gap_1()\n                                            .child(\n                                                div()\n                                                    .overflow_x_hidden()\n                                                    .child(builder.render(window, cx)),\n                                            )\n                                            .when(self.required, |this| {\n                                                this.child(\n                                                    div().text_color(cx.theme().danger).child(\"*\"),\n                                                )\n                                            }),\n                                    )\n                                }),\n                        )\n                    })\n                    .child(\n                        div()\n                            .w_full()\n                            .flex_1()\n                            .overflow_x_hidden()\n                            .children(self.children),\n                    ),\n            )\n            .child(\n                // Other\n                wrap_div(layout)\n                    .gap(inner_gap)\n                    .when(has_label && layout.is_horizontal(), |this| {\n                        this.child(\n                            // Empty for spacing to align with the input\n                            wrap_label(label_width),\n                        )\n                    })\n                    .when_some(self.description, |this, builder| {\n                        this.child(\n                            div()\n                                .text_xs()\n                                .text_color(cx.theme().muted_foreground)\n                                .child(builder.render(window, cx)),\n                        )\n                    }),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/form/form.rs",
    "content": "use gpui::{\n    App, Axis, IntoElement, ParentElement, Pixels, Rems, RenderOnce, StyleRefinement, Styled,\n    Window, div, px,\n};\n\nuse crate::{\n    Sizable, Size,\n    form::{Field, FieldProps},\n    v_flex,\n};\n\n/// A form element that contains multiple form fields.\n#[derive(IntoElement)]\npub struct Form {\n    style: StyleRefinement,\n    fields: Vec<Field>,\n    props: FieldProps,\n}\n\nimpl Form {\n    fn new() -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            props: FieldProps::default(),\n            fields: Vec::new(),\n        }\n    }\n\n    /// Creates a new form with a horizontal layout.\n    pub fn horizontal() -> Self {\n        Self::new().layout(Axis::Horizontal)\n    }\n\n    /// Creates a new form with a vertical layout.\n    pub fn vertical() -> Self {\n        Self::new().layout(Axis::Vertical)\n    }\n\n    /// Set the layout for the form, default is `Axis::Vertical`.\n    pub fn layout(mut self, layout: Axis) -> Self {\n        self.props.layout = layout;\n        self\n    }\n\n    /// Set the width of the labels in the form. Default is `px(100.)`.\n    pub fn label_width(mut self, width: Pixels) -> Self {\n        self.props.label_width = Some(width);\n        self\n    }\n\n    /// Set the text size of the labels in the form. Default is `None`.\n    pub fn label_text_size(mut self, size: Rems) -> Self {\n        self.props.label_text_size = Some(size);\n        self\n    }\n\n    /// Add a child to the form.\n    pub fn child(mut self, field: impl Into<Field>) -> Self {\n        self.fields.push(field.into());\n        self\n    }\n\n    /// Add multiple children to the form.\n    pub fn children(mut self, fields: impl IntoIterator<Item = Field>) -> Self {\n        self.fields.extend(fields);\n        self\n    }\n\n    /// Set the column count for the form.\n    ///\n    /// Default is 1.\n    pub fn columns(mut self, columns: usize) -> Self {\n        self.props.columns = columns;\n        self\n    }\n}\n\nimpl Styled for Form {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Sizable for Form {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.props.size = size.into();\n        self\n    }\n}\n\nimpl RenderOnce for Form {\n    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {\n        let props = self.props;\n\n        let gap = match props.size {\n            Size::XSmall | Size::Small => px(6.),\n            Size::Large => px(12.),\n            _ => px(8.),\n        };\n\n        // Add `div` wrapper to avoid sometime width not full issue.\n        div().child(\n            v_flex()\n                .w_full()\n                .gap_x(gap * 3.)\n                .gap_y(gap)\n                .grid()\n                .grid_cols(props.columns as u16)\n                .children(\n                    self.fields\n                        .into_iter()\n                        .enumerate()\n                        .map(|(ix, field)| field.props(ix, props)),\n                ),\n        )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/form/mod.rs",
    "content": "mod field;\nmod form;\n\npub use field::*;\npub use form::*;\n\n/// Create a new [`Form`] with a vertical layout.\npub fn v_form() -> Form {\n    Form::vertical()\n}\n\n/// Create a new [`Form`] with a horizontal layout.\npub fn h_form() -> Form {\n    Form::horizontal()\n}\n\n/// Create a new [`Field`].\npub fn field() -> Field {\n    Field::new()\n}\n"
  },
  {
    "path": "crates/ui/src/geometry.rs",
    "content": "use std::fmt::{self, Debug, Display, Formatter};\n\nuse gpui::{AbsoluteLength, Axis, Corner, Length, Pixels};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\n/// A enum for defining the placement of the element.\n///\n/// See also: [`Side`] if you need to define the left, right side.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]\npub enum Placement {\n    #[serde(rename = \"top\")]\n    Top,\n    #[serde(rename = \"bottom\")]\n    Bottom,\n    #[serde(rename = \"left\")]\n    Left,\n    #[serde(rename = \"right\")]\n    Right,\n}\n\nimpl Display for Placement {\n    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n        match self {\n            Placement::Top => write!(f, \"Top\"),\n            Placement::Bottom => write!(f, \"Bottom\"),\n            Placement::Left => write!(f, \"Left\"),\n            Placement::Right => write!(f, \"Right\"),\n        }\n    }\n}\n\nimpl Placement {\n    #[inline]\n    pub fn is_horizontal(&self) -> bool {\n        match self {\n            Placement::Left | Placement::Right => true,\n            _ => false,\n        }\n    }\n\n    #[inline]\n    pub fn is_vertical(&self) -> bool {\n        match self {\n            Placement::Top | Placement::Bottom => true,\n            _ => false,\n        }\n    }\n\n    #[inline]\n    pub fn axis(&self) -> Axis {\n        match self {\n            Placement::Top | Placement::Bottom => Axis::Vertical,\n            Placement::Left | Placement::Right => Axis::Horizontal,\n        }\n    }\n}\n\n/// The anchor position of an element.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]\npub enum Anchor {\n    #[default]\n    #[serde(rename = \"top-left\")]\n    TopLeft,\n    #[serde(rename = \"top-center\")]\n    TopCenter,\n    #[serde(rename = \"top-right\")]\n    TopRight,\n    #[serde(rename = \"bottom-left\")]\n    BottomLeft,\n    #[serde(rename = \"bottom-center\")]\n    BottomCenter,\n    #[serde(rename = \"bottom-right\")]\n    BottomRight,\n}\n\nimpl Display for Anchor {\n    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n        match self {\n            Anchor::TopLeft => write!(f, \"TopLeft\"),\n            Anchor::TopCenter => write!(f, \"TopCenter\"),\n            Anchor::TopRight => write!(f, \"TopRight\"),\n            Anchor::BottomLeft => write!(f, \"BottomLeft\"),\n            Anchor::BottomCenter => write!(f, \"BottomCenter\"),\n            Anchor::BottomRight => write!(f, \"BottomRight\"),\n        }\n    }\n}\n\nimpl Anchor {\n    /// Returns true if the anchor is at the top.\n    #[inline]\n    pub fn is_top(&self) -> bool {\n        matches!(self, Self::TopLeft | Self::TopCenter | Self::TopRight)\n    }\n\n    /// Returns true if the anchor is at the bottom.\n    #[inline]\n    pub fn is_bottom(&self) -> bool {\n        matches!(\n            self,\n            Self::BottomLeft | Self::BottomCenter | Self::BottomRight\n        )\n    }\n\n    /// Returns true if the anchor is at the left.\n    #[inline]\n    pub fn is_left(&self) -> bool {\n        matches!(self, Self::TopLeft | Self::BottomLeft)\n    }\n\n    /// Returns true if the anchor is at the right.\n    #[inline]\n    pub fn is_right(&self) -> bool {\n        matches!(self, Self::TopRight | Self::BottomRight)\n    }\n\n    /// Returns true if the anchor is at the center.\n    #[inline]\n    pub fn is_center(&self) -> bool {\n        matches!(self, Self::TopCenter | Self::BottomCenter)\n    }\n\n    /// Swaps the vertical position of the anchor.\n    pub fn swap_vertical(&self) -> Self {\n        match self {\n            Anchor::TopLeft => Anchor::BottomLeft,\n            Anchor::TopCenter => Anchor::BottomCenter,\n            Anchor::TopRight => Anchor::BottomRight,\n            Anchor::BottomLeft => Anchor::TopLeft,\n            Anchor::BottomCenter => Anchor::TopCenter,\n            Anchor::BottomRight => Anchor::TopRight,\n        }\n    }\n\n    /// Swaps the horizontal position of the anchor.\n    pub fn swap_horizontal(&self) -> Self {\n        match self {\n            Anchor::TopLeft => Anchor::TopRight,\n            Anchor::TopCenter => Anchor::TopCenter,\n            Anchor::TopRight => Anchor::TopLeft,\n            Anchor::BottomLeft => Anchor::BottomRight,\n            Anchor::BottomCenter => Anchor::BottomCenter,\n            Anchor::BottomRight => Anchor::BottomLeft,\n        }\n    }\n\n    pub(crate) fn other_side_corner_along(&self, axis: Axis) -> Anchor {\n        match axis {\n            Axis::Vertical => match self {\n                Self::TopLeft => Self::BottomLeft,\n                Self::TopCenter => Self::BottomCenter,\n                Self::TopRight => Self::BottomRight,\n                Self::BottomLeft => Self::TopLeft,\n                Self::BottomCenter => Self::TopCenter,\n                Self::BottomRight => Self::TopRight,\n            },\n            Axis::Horizontal => match self {\n                Self::TopLeft => Self::TopRight,\n                Self::TopCenter => Self::TopCenter,\n                Self::TopRight => Self::TopLeft,\n                Self::BottomLeft => Self::BottomRight,\n                Self::BottomCenter => Self::BottomCenter,\n                Self::BottomRight => Self::BottomLeft,\n            },\n        }\n    }\n}\n\nimpl From<Corner> for Anchor {\n    fn from(corner: Corner) -> Self {\n        match corner {\n            Corner::TopLeft => Anchor::TopLeft,\n            Corner::TopRight => Anchor::TopRight,\n            Corner::BottomLeft => Anchor::BottomLeft,\n            Corner::BottomRight => Anchor::BottomRight,\n        }\n    }\n}\n\nimpl From<Anchor> for Corner {\n    fn from(anchor: Anchor) -> Self {\n        match anchor {\n            Anchor::TopLeft => Corner::TopLeft,\n            Anchor::TopRight => Corner::TopRight,\n            Anchor::BottomLeft => Corner::BottomLeft,\n            Anchor::BottomRight => Corner::BottomRight,\n            Anchor::TopCenter => Corner::TopLeft,\n            Anchor::BottomCenter => Corner::BottomLeft,\n        }\n    }\n}\n\n/// A enum for defining the side of the element.\n///\n/// See also: [`Placement`] if you need to define the 4 edges.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]\npub enum Side {\n    #[serde(rename = \"left\")]\n    Left,\n    #[serde(rename = \"right\")]\n    Right,\n}\n\nimpl Side {\n    /// Returns true if the side is left.\n    #[inline]\n    pub fn is_left(&self) -> bool {\n        matches!(self, Self::Left)\n    }\n\n    /// Returns true if the side is right.\n    #[inline]\n    pub fn is_right(&self) -> bool {\n        matches!(self, Self::Right)\n    }\n}\n\n/// A trait to extend the [`Axis`] enum with utility methods.\npub trait AxisExt {\n    fn is_horizontal(self) -> bool;\n    fn is_vertical(self) -> bool;\n}\n\nimpl AxisExt for Axis {\n    #[inline]\n    fn is_horizontal(self) -> bool {\n        self == Axis::Horizontal\n    }\n\n    #[inline]\n    fn is_vertical(self) -> bool {\n        self == Axis::Vertical\n    }\n}\n\n/// A trait to extend the [`Length`] enum with utility methods.\npub trait LengthExt {\n    /// Converts the [`Length`] to [`Pixels`] based on a given `base_size` and `rem_size`.\n    ///\n    /// If the [`Length`] is [`Length::Auto`], it returns `None`.\n    fn to_pixels(&self, base_size: AbsoluteLength, rem_size: Pixels) -> Option<Pixels>;\n}\n\nimpl LengthExt for Length {\n    fn to_pixels(&self, base_size: AbsoluteLength, rem_size: Pixels) -> Option<Pixels> {\n        match self {\n            Length::Auto => None,\n            Length::Definite(len) => Some(len.to_pixels(base_size, rem_size)),\n        }\n    }\n}\n\n/// A struct for defining the edges of an element.\n///\n/// A extend version of [`gpui::Edges`] to serialize/deserialize.\n#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, Eq, PartialEq)]\n#[repr(C)]\npub struct Edges<T: Clone + Debug + Default + PartialEq> {\n    /// The size of the top edge.\n    pub top: T,\n    /// The size of the right edge.\n    pub right: T,\n    /// The size of the bottom edge.\n    pub bottom: T,\n    /// The size of the left edge.\n    pub left: T,\n}\n\nimpl<T> Edges<T>\nwhere\n    T: Clone + Debug + Default + PartialEq,\n{\n    /// Creates a new `Edges` instance with all edges set to the same value.\n    pub fn all(value: T) -> Self {\n        Self {\n            top: value.clone(),\n            right: value.clone(),\n            bottom: value.clone(),\n            left: value,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use gpui::px;\n\n    use super::Placement;\n    #[test]\n    fn test_placement() {\n        assert!(Placement::Left.is_horizontal());\n        assert!(Placement::Right.is_horizontal());\n        assert!(!Placement::Top.is_horizontal());\n        assert!(!Placement::Bottom.is_horizontal());\n\n        assert!(Placement::Top.is_vertical());\n        assert!(Placement::Bottom.is_vertical());\n        assert!(!Placement::Left.is_vertical());\n        assert!(!Placement::Right.is_vertical());\n\n        assert_eq!(Placement::Top.axis(), gpui::Axis::Vertical);\n        assert_eq!(Placement::Bottom.axis(), gpui::Axis::Vertical);\n        assert_eq!(Placement::Left.axis(), gpui::Axis::Horizontal);\n        assert_eq!(Placement::Right.axis(), gpui::Axis::Horizontal);\n\n        assert_eq!(Placement::Top.to_string(), \"Top\");\n        assert_eq!(Placement::Bottom.to_string(), \"Bottom\");\n        assert_eq!(Placement::Left.to_string(), \"Left\");\n        assert_eq!(Placement::Right.to_string(), \"Right\");\n\n        assert_eq!(serde_json::to_string(&Placement::Top).unwrap(), r#\"\"top\"\"#);\n        assert_eq!(\n            serde_json::to_string(&Placement::Bottom).unwrap(),\n            r#\"\"bottom\"\"#\n        );\n        assert_eq!(\n            serde_json::to_string(&Placement::Left).unwrap(),\n            r#\"\"left\"\"#\n        );\n        assert_eq!(\n            serde_json::to_string(&Placement::Right).unwrap(),\n            r#\"\"right\"\"#\n        );\n\n        assert_eq!(\n            serde_json::from_str::<Placement>(r#\"\"top\"\"#).unwrap(),\n            Placement::Top\n        );\n        assert_eq!(\n            serde_json::from_str::<Placement>(r#\"\"bottom\"\"#).unwrap(),\n            Placement::Bottom\n        );\n        assert_eq!(\n            serde_json::from_str::<Placement>(r#\"\"left\"\"#).unwrap(),\n            Placement::Left\n        );\n        assert_eq!(\n            serde_json::from_str::<Placement>(r#\"\"right\"\"#).unwrap(),\n            Placement::Right\n        );\n    }\n\n    #[test]\n    fn test_side() {\n        use super::Side;\n        let left = Side::Left;\n        let right = Side::Right;\n\n        assert!(left.is_left());\n        assert!(!left.is_right());\n\n        assert!(right.is_right());\n        assert!(!right.is_left());\n\n        // Test serialization\n        assert_eq!(serde_json::to_string(&left).unwrap(), r#\"\"left\"\"#);\n        assert_eq!(serde_json::to_string(&right).unwrap(), r#\"\"right\"\"#);\n        assert_eq!(\n            serde_json::from_str::<Side>(r#\"\"left\"\"#).unwrap(),\n            Side::Left\n        );\n        assert_eq!(\n            serde_json::from_str::<Side>(r#\"\"right\"\"#).unwrap(),\n            Side::Right\n        );\n    }\n\n    #[test]\n    fn test_anchor() {\n        use super::Anchor;\n\n        assert_eq!(Anchor::default(), Anchor::TopLeft);\n\n        assert_eq!(Anchor::TopLeft.to_string(), \"TopLeft\");\n        assert_eq!(Anchor::TopCenter.to_string(), \"TopCenter\");\n        assert_eq!(Anchor::TopRight.to_string(), \"TopRight\");\n        assert_eq!(Anchor::BottomLeft.to_string(), \"BottomLeft\");\n        assert_eq!(Anchor::BottomCenter.to_string(), \"BottomCenter\");\n        assert_eq!(Anchor::BottomRight.to_string(), \"BottomRight\");\n\n        // Test serialization\n        assert_eq!(\n            serde_json::to_string(&Anchor::TopLeft).unwrap(),\n            r#\"\"top-left\"\"#\n        );\n        assert_eq!(\n            serde_json::to_string(&Anchor::TopCenter).unwrap(),\n            r#\"\"top-center\"\"#\n        );\n        assert_eq!(\n            serde_json::to_string(&Anchor::TopRight).unwrap(),\n            r#\"\"top-right\"\"#\n        );\n        assert_eq!(\n            serde_json::to_string(&Anchor::BottomLeft).unwrap(),\n            r#\"\"bottom-left\"\"#\n        );\n        assert_eq!(\n            serde_json::to_string(&Anchor::BottomCenter).unwrap(),\n            r#\"\"bottom-center\"\"#\n        );\n        assert_eq!(\n            serde_json::to_string(&Anchor::BottomRight).unwrap(),\n            r#\"\"bottom-right\"\"#\n        );\n\n        // Test deserialization\n        assert_eq!(\n            serde_json::from_str::<Anchor>(r#\"\"top-left\"\"#).unwrap(),\n            Anchor::TopLeft\n        );\n        assert_eq!(\n            serde_json::from_str::<Anchor>(r#\"\"top-center\"\"#).unwrap(),\n            Anchor::TopCenter\n        );\n        assert_eq!(\n            serde_json::from_str::<Anchor>(r#\"\"top-right\"\"#).unwrap(),\n            Anchor::TopRight\n        );\n        assert_eq!(\n            serde_json::from_str::<Anchor>(r#\"\"bottom-left\"\"#).unwrap(),\n            Anchor::BottomLeft\n        );\n        assert_eq!(\n            serde_json::from_str::<Anchor>(r#\"\"bottom-center\"\"#).unwrap(),\n            Anchor::BottomCenter\n        );\n        assert_eq!(\n            serde_json::from_str::<Anchor>(r#\"\"bottom-right\"\"#).unwrap(),\n            Anchor::BottomRight\n        );\n\n        // Test is_top\n        assert!(Anchor::TopLeft.is_top());\n        assert!(Anchor::TopCenter.is_top());\n        assert!(Anchor::TopRight.is_top());\n        assert!(!Anchor::BottomLeft.is_top());\n        assert!(!Anchor::BottomCenter.is_top());\n        assert!(!Anchor::BottomRight.is_top());\n\n        // Test is_bottom\n        assert!(Anchor::BottomLeft.is_bottom());\n        assert!(Anchor::BottomCenter.is_bottom());\n        assert!(Anchor::BottomRight.is_bottom());\n        assert!(!Anchor::TopLeft.is_bottom());\n        assert!(!Anchor::TopCenter.is_bottom());\n        assert!(!Anchor::TopRight.is_bottom());\n\n        // Test is_left\n        assert!(Anchor::TopLeft.is_left());\n        assert!(Anchor::BottomLeft.is_left());\n        assert!(!Anchor::TopCenter.is_left());\n        assert!(!Anchor::BottomCenter.is_left());\n        assert!(!Anchor::TopRight.is_left());\n        assert!(!Anchor::BottomRight.is_left());\n\n        // Test is_right\n        assert!(Anchor::TopRight.is_right());\n        assert!(Anchor::BottomRight.is_right());\n        assert!(!Anchor::TopLeft.is_right());\n        assert!(!Anchor::BottomLeft.is_right());\n        assert!(!Anchor::TopCenter.is_right());\n        assert!(!Anchor::BottomCenter.is_right());\n\n        // Test is_center\n        assert!(Anchor::TopCenter.is_center());\n        assert!(Anchor::BottomCenter.is_center());\n        assert!(!Anchor::TopLeft.is_center());\n        assert!(!Anchor::TopRight.is_center());\n        assert!(!Anchor::BottomLeft.is_center());\n        assert!(!Anchor::BottomRight.is_center());\n    }\n\n    #[test]\n    fn test_anchor_swap_vertical() {\n        use super::Anchor;\n\n        // Test swap_vertical\n        assert_eq!(Anchor::TopLeft.swap_vertical(), Anchor::BottomLeft);\n        assert_eq!(Anchor::TopCenter.swap_vertical(), Anchor::BottomCenter);\n        assert_eq!(Anchor::TopRight.swap_vertical(), Anchor::BottomRight);\n        assert_eq!(Anchor::BottomLeft.swap_vertical(), Anchor::TopLeft);\n        assert_eq!(Anchor::BottomCenter.swap_vertical(), Anchor::TopCenter);\n        assert_eq!(Anchor::BottomRight.swap_vertical(), Anchor::TopRight);\n\n        // Test double swap returns to original\n        assert_eq!(\n            Anchor::TopLeft.swap_vertical().swap_vertical(),\n            Anchor::TopLeft\n        );\n        assert_eq!(\n            Anchor::TopCenter.swap_vertical().swap_vertical(),\n            Anchor::TopCenter\n        );\n        assert_eq!(\n            Anchor::BottomRight.swap_vertical().swap_vertical(),\n            Anchor::BottomRight\n        );\n    }\n\n    #[test]\n    fn test_anchor_swap_horizontal() {\n        use super::Anchor;\n\n        // Test swap_horizontal\n        assert_eq!(Anchor::TopLeft.swap_horizontal(), Anchor::TopRight);\n        assert_eq!(Anchor::TopCenter.swap_horizontal(), Anchor::TopCenter);\n        assert_eq!(Anchor::TopRight.swap_horizontal(), Anchor::TopLeft);\n        assert_eq!(Anchor::BottomLeft.swap_horizontal(), Anchor::BottomRight);\n        assert_eq!(Anchor::BottomCenter.swap_horizontal(), Anchor::BottomCenter);\n        assert_eq!(Anchor::BottomRight.swap_horizontal(), Anchor::BottomLeft);\n\n        // Test double swap returns to original\n        assert_eq!(\n            Anchor::TopLeft.swap_horizontal().swap_horizontal(),\n            Anchor::TopLeft\n        );\n        assert_eq!(\n            Anchor::BottomRight.swap_horizontal().swap_horizontal(),\n            Anchor::BottomRight\n        );\n        // Center positions should remain unchanged\n        assert_eq!(Anchor::TopCenter.swap_horizontal(), Anchor::TopCenter);\n        assert_eq!(Anchor::BottomCenter.swap_horizontal(), Anchor::BottomCenter);\n    }\n\n    #[test]\n    fn test_anchor_from_corner() {\n        use super::Anchor;\n        use gpui::Corner;\n\n        // Test From<Corner> for Anchor\n        assert_eq!(Anchor::from(Corner::TopLeft), Anchor::TopLeft);\n        assert_eq!(Anchor::from(Corner::TopRight), Anchor::TopRight);\n        assert_eq!(Anchor::from(Corner::BottomLeft), Anchor::BottomLeft);\n        assert_eq!(Anchor::from(Corner::BottomRight), Anchor::BottomRight);\n\n        // Test using into()\n        let anchor: Anchor = Corner::TopLeft.into();\n        assert_eq!(anchor, Anchor::TopLeft);\n\n        let anchor: Anchor = Corner::BottomRight.into();\n        assert_eq!(anchor, Anchor::BottomRight);\n    }\n\n    #[test]\n    fn test_anchor_to_corner() {\n        use super::Anchor;\n        use gpui::Corner;\n\n        // Test From<Anchor> for Corner (i.e., Into<Corner>)\n        assert_eq!(Corner::from(Anchor::TopLeft), Corner::TopLeft);\n        assert_eq!(Corner::from(Anchor::TopRight), Corner::TopRight);\n        assert_eq!(Corner::from(Anchor::BottomLeft), Corner::BottomLeft);\n        assert_eq!(Corner::from(Anchor::BottomRight), Corner::BottomRight);\n\n        // Test center anchors map to their respective corners\n        assert_eq!(Corner::from(Anchor::TopCenter), Corner::TopLeft);\n        assert_eq!(Corner::from(Anchor::BottomCenter), Corner::BottomLeft);\n\n        // Test using into()\n        let corner: Corner = Anchor::TopLeft.into();\n        assert_eq!(corner, Corner::TopLeft);\n\n        let corner: Corner = Anchor::TopCenter.into();\n        assert_eq!(corner, Corner::TopLeft);\n\n        let corner: Corner = Anchor::BottomRight.into();\n        assert_eq!(corner, Corner::BottomRight);\n    }\n\n    #[test]\n    fn test_edges_pixels() {\n        use super::Edges;\n        use gpui::Pixels;\n\n        let edge_value = px(10.0);\n        let edges = Edges::all(edge_value);\n\n        assert_eq!(edges.top, edge_value);\n        assert_eq!(edges.right, edge_value);\n        assert_eq!(edges.bottom, edge_value);\n        assert_eq!(edges.left, edge_value);\n\n        let custom_edges = Edges {\n            top: px(5.0),\n            right: px(10.0),\n            bottom: px(15.0),\n            left: px(20.0),\n        };\n\n        assert_eq!(custom_edges.top, px(5.0));\n        assert_eq!(custom_edges.right, px(10.0));\n        assert_eq!(custom_edges.bottom, px(15.0));\n        assert_eq!(custom_edges.left, px(20.0));\n\n        // Test serialization\n        let serialized = serde_json::to_string(&custom_edges).unwrap();\n        assert_eq!(\n            serialized,\n            r#\"{\"top\":5.0,\"right\":10.0,\"bottom\":15.0,\"left\":20.0}\"#\n        );\n\n        // Test deserialization\n        let deserialized: Edges<Pixels> = serde_json::from_str(&serialized).unwrap();\n        assert_eq!(deserialized, custom_edges);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/global_state.rs",
    "content": "use gpui::{App, ElementId, Entity, FocusHandle, Global, OwnedMenu};\nuse std::collections::HashSet;\n\nuse crate::text::TextViewState;\n\npub(crate) fn init(cx: &mut App) {\n    cx.set_global(GlobalState::new());\n}\n\nimpl Global for GlobalState {}\n\npub struct GlobalState {\n    pub(crate) text_view_state_stack: Vec<Entity<TextViewState>>,\n    /// Set of open popover IDs that use deferred rendering.\n    /// When this set is not empty, we are inside at least one deferred context.\n    /// This is used to prevent double-deferred elements which would cause GPUI to panic.\n    open_deferred_popovers: HashSet<ElementId>,\n    /// Application menus storage\n    app_menus: Vec<OwnedMenu>,\n}\n\nimpl GlobalState {\n    pub(crate) fn new() -> Self {\n        Self {\n            text_view_state_stack: Vec::new(),\n            open_deferred_popovers: HashSet::new(),\n            app_menus: Vec::new(),\n        }\n    }\n\n    pub fn global(cx: &App) -> &Self {\n        cx.global::<Self>()\n    }\n\n    pub fn global_mut(cx: &mut App) -> &mut Self {\n        cx.global_mut::<Self>()\n    }\n\n    pub(crate) fn text_view_state(&self) -> Option<&Entity<TextViewState>> {\n        self.text_view_state_stack.last()\n    }\n\n    /// Check if we are currently inside a deferred context (e.g., inside an open Popover).\n    pub(crate) fn is_in_deferred_context(&self) -> bool {\n        !self.open_deferred_popovers.is_empty()\n    }\n\n    /// Register a popover that uses deferred rendering as open.\n    pub(crate) fn register_deferred_popover(&mut self, focus_handle: &FocusHandle) {\n        self.open_deferred_popovers\n            .insert(format!(\"{focus_handle:?}\").into());\n    }\n\n    /// Unregister a popover when it closes.\n    pub(crate) fn unregister_deferred_popover(&mut self, focus_handle: &FocusHandle) {\n        let element_id: ElementId = format!(\"{focus_handle:?}\").into();\n        self.open_deferred_popovers.remove(&element_id);\n    }\n\n    /// Get the application menus\n    pub fn app_menus(&self) -> &[OwnedMenu] {\n        &self.app_menus\n    }\n\n    /// Set the application menus\n    pub fn set_app_menus(&mut self, menus: Vec<OwnedMenu>) {\n        self.app_menus = menus;\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/group_box.rs",
    "content": "use gpui::{\n    AnyElement, App, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,\n    StyleRefinement, Styled, Window, div, prelude::FluentBuilder, relative,\n};\nuse smallvec::SmallVec;\n\nuse crate::{ActiveTheme, StyledExt as _, v_flex};\n\n/// The variant of the GroupBox.\n#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)]\npub enum GroupBoxVariant {\n    #[default]\n    Normal,\n    Fill,\n    Outline,\n}\n\n/// Trait to add GroupBox variant methods to elements.\npub trait GroupBoxVariants: Sized {\n    /// Set the variant of the [`GroupBox`].\n    fn with_variant(self, variant: GroupBoxVariant) -> Self;\n    /// Set to use [`GroupBoxVariant::Normal`] to GroupBox.\n    fn normal(mut self) -> Self {\n        self = self.with_variant(GroupBoxVariant::Normal);\n        self\n    }\n    /// Set to use [`GroupBoxVariant::Fill`] to GroupBox.\n    fn fill(mut self) -> Self {\n        self = self.with_variant(GroupBoxVariant::Fill);\n        self\n    }\n    /// Set to use [`GroupBoxVariant::Outline`] to GroupBox.\n    fn outline(mut self) -> Self {\n        self = self.with_variant(GroupBoxVariant::Outline);\n        self\n    }\n}\n\nimpl GroupBoxVariant {\n    /// Create a GroupBoxVariant from a string.\n    pub fn from_str(s: &str) -> Self {\n        match s.to_lowercase().as_str() {\n            \"fill\" => GroupBoxVariant::Fill,\n            \"outline\" => GroupBoxVariant::Outline,\n            _ => GroupBoxVariant::Normal,\n        }\n    }\n\n    /// Convert the GroupBoxVariant to a string.\n    pub fn as_str(&self) -> &str {\n        match self {\n            GroupBoxVariant::Normal => \"normal\",\n            GroupBoxVariant::Fill => \"fill\",\n            GroupBoxVariant::Outline => \"outline\",\n        }\n    }\n}\n\n/// GroupBox is a styled container element that with\n/// an optional title to groups related content together.\n#[derive(IntoElement)]\npub struct GroupBox {\n    id: Option<ElementId>,\n    variant: GroupBoxVariant,\n    style: StyleRefinement,\n    title_style: StyleRefinement,\n    title: Option<AnyElement>,\n    content_style: StyleRefinement,\n    children: SmallVec<[AnyElement; 1]>,\n}\n\nimpl GroupBox {\n    /// Create a new GroupBox.\n    pub fn new() -> Self {\n        Self {\n            id: None,\n            variant: GroupBoxVariant::default(),\n            style: StyleRefinement::default(),\n            title_style: StyleRefinement::default(),\n            content_style: StyleRefinement::default(),\n            title: None,\n            children: SmallVec::new(),\n        }\n    }\n\n    /// Set the id of the group box, default is None.\n    pub fn id(mut self, id: impl Into<ElementId>) -> Self {\n        self.id = Some(id.into());\n        self\n    }\n\n    /// Set the title of the group box, default is None.\n    pub fn title(mut self, title: impl IntoElement) -> Self {\n        self.title = Some(title.into_any_element());\n        self\n    }\n\n    /// Set the style of the title of the group box to override the default style, default is None.\n    pub fn title_style(mut self, style: StyleRefinement) -> Self {\n        self.title_style = style;\n        self\n    }\n\n    /// Set the style of the content of the group box to override the default style, default is None.\n    pub fn content_style(mut self, style: StyleRefinement) -> Self {\n        self.content_style = style;\n        self\n    }\n}\n\nimpl ParentElement for GroupBox {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Styled for GroupBox {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl GroupBoxVariants for GroupBox {\n    fn with_variant(mut self, variant: GroupBoxVariant) -> Self {\n        self.variant = variant;\n        self\n    }\n}\n\nimpl RenderOnce for GroupBox {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let (bg, border, has_paddings) = match self.variant {\n            GroupBoxVariant::Normal => (None, None, false),\n            GroupBoxVariant::Fill => (Some(cx.theme().group_box), None, true),\n            GroupBoxVariant::Outline => (None, Some(cx.theme().border), true),\n        };\n\n        // Add `div` wrapper to avoid sometime width not full issue.\n        div().child(\n            v_flex()\n                .id(self.id.unwrap_or(\"group-box\".into()))\n                .w_full()\n                .when(has_paddings, |this| this.gap_3())\n                .when(!has_paddings, |this| this.gap_4())\n                .refine_style(&self.style)\n                .when_some(self.title, |this, title| {\n                    this.child(\n                        div()\n                            .text_color(cx.theme().muted_foreground)\n                            .line_height(relative(1.))\n                            .refine_style(&self.title_style)\n                            .child(title),\n                    )\n                })\n                .child(\n                    v_flex()\n                        .when_some(bg, |this, bg| this.bg(bg))\n                        .when_some(border, |this, border| this.border_color(border).border_1())\n                        .text_color(cx.theme().group_box_foreground)\n                        .when(has_paddings, |this| this.p_4())\n                        .gap_4()\n                        .rounded(cx.theme().radius)\n                        .refine_style(&self.content_style)\n                        .children(self.children),\n                ),\n        )\n    }\n}\n\n#[cfg(test)]\nmod test {\n    #[test]\n    fn test_group_variant_from_str() {\n        use super::GroupBoxVariant;\n\n        assert_eq!(GroupBoxVariant::from_str(\"normal\"), GroupBoxVariant::Normal);\n        assert_eq!(GroupBoxVariant::from_str(\"fill\"), GroupBoxVariant::Fill);\n        assert_eq!(\n            GroupBoxVariant::from_str(\"outline\"),\n            GroupBoxVariant::Outline\n        );\n        assert_eq!(GroupBoxVariant::from_str(\"other\"), GroupBoxVariant::Normal);\n\n        assert_eq!(GroupBoxVariant::from_str(\"FILL\"), GroupBoxVariant::Fill);\n        assert_eq!(\n            GroupBoxVariant::from_str(\"OutLine\"),\n            GroupBoxVariant::Outline\n        );\n\n        assert_eq!(GroupBoxVariant::Normal.as_str(), \"normal\");\n        assert_eq!(GroupBoxVariant::Fill.as_str(), \"fill\");\n        assert_eq!(GroupBoxVariant::Outline.as_str(), \"outline\");\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/highlighter/diagnostics.rs",
    "content": "use std::{\n    cmp::Ordering,\n    ops::{Deref, Range},\n    usize,\n};\n\nuse gpui::{px, App, HighlightStyle, Hsla, SharedString, UnderlineStyle};\nuse ropey::Rope;\nuse sum_tree::{Bias, SeekTarget, SumTree};\n\nuse crate::{\n    input::{Position, RopeExt as _},\n    ActiveTheme,\n};\n\npub type DiagnosticRelatedInformation = lsp_types::DiagnosticRelatedInformation;\npub type CodeDescription = lsp_types::CodeDescription;\npub type RelatedInformation = lsp_types::DiagnosticRelatedInformation;\npub type DiagnosticTag = lsp_types::DiagnosticTag;\n\n#[derive(Debug, Eq, PartialEq, Clone, Default)]\npub struct Diagnostic {\n    /// The range [`Position`] at which the message applies.\n    ///\n    /// This is the column, character range within a single line.\n    pub range: Range<Position>,\n\n    /// The diagnostic's severity. Can be omitted. If omitted it is up to the\n    /// client to interpret diagnostics as error, warning, info or hint.\n    pub severity: DiagnosticSeverity,\n\n    /// The diagnostic's code. Can be omitted.\n    pub code: Option<SharedString>,\n\n    pub code_description: Option<CodeDescription>,\n\n    /// A human-readable string describing the source of this\n    /// diagnostic, e.g. 'typescript' or 'super lint'.\n    pub source: Option<SharedString>,\n\n    /// The diagnostic's message.\n    pub message: SharedString,\n\n    /// An array of related diagnostic information, e.g. when symbol-names within\n    /// a scope collide all definitions can be marked via this property.\n    pub related_information: Option<Vec<DiagnosticRelatedInformation>>,\n\n    /// Additional metadata about the diagnostic.\n    pub tags: Option<Vec<DiagnosticTag>>,\n\n    /// A data entry field that is preserved between a `textDocument/publishDiagnostics`\n    /// notification and `textDocument/codeAction` request.\n    ///\n    /// @since 3.16.0\n    pub data: Option<serde_json::Value>,\n}\n\nimpl From<lsp_types::Diagnostic> for Diagnostic {\n    fn from(value: lsp_types::Diagnostic) -> Self {\n        Self {\n            range: value.range.start..value.range.end,\n            severity: value\n                .severity\n                .map(Into::into)\n                .unwrap_or(DiagnosticSeverity::Info),\n            code: value.code.map(|c| match c {\n                lsp_types::NumberOrString::Number(n) => SharedString::from(n.to_string()),\n                lsp_types::NumberOrString::String(s) => SharedString::from(s),\n            }),\n            code_description: value.code_description,\n            source: value.source.map(|s| s.into()),\n            message: value.message.into(),\n            related_information: value.related_information,\n            tags: value.tags,\n            data: value.data,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\npub enum DiagnosticSeverity {\n    #[default]\n    Hint,\n    Error,\n    Warning,\n    Info,\n}\n\nimpl From<lsp_types::DiagnosticSeverity> for DiagnosticSeverity {\n    fn from(value: lsp_types::DiagnosticSeverity) -> Self {\n        match value {\n            lsp_types::DiagnosticSeverity::ERROR => Self::Error,\n            lsp_types::DiagnosticSeverity::WARNING => Self::Warning,\n            lsp_types::DiagnosticSeverity::INFORMATION => Self::Info,\n            lsp_types::DiagnosticSeverity::HINT => Self::Hint,\n            _ => Self::Info, // Default to Info if unknown\n        }\n    }\n}\n\nimpl DiagnosticSeverity {\n    pub(crate) fn bg(&self, cx: &App) -> Hsla {\n        let theme = &cx.theme().highlight_theme;\n\n        match self {\n            Self::Error => theme.style.status.error_background(cx),\n            Self::Warning => theme.style.status.warning_background(cx),\n            Self::Info => theme.style.status.info_background(cx),\n            Self::Hint => theme.style.status.hint_background(cx),\n        }\n    }\n\n    pub(crate) fn fg(&self, cx: &App) -> Hsla {\n        let theme = &cx.theme().highlight_theme;\n\n        match self {\n            Self::Error => theme.style.status.error(cx),\n            Self::Warning => theme.style.status.warning(cx),\n            Self::Info => theme.style.status.info(cx),\n            Self::Hint => theme.style.status.hint(cx),\n        }\n    }\n\n    pub(crate) fn border(&self, cx: &App) -> Hsla {\n        let theme = &cx.theme().highlight_theme;\n        match self {\n            Self::Error => theme.style.status.error_border(cx),\n            Self::Warning => theme.style.status.warning_border(cx),\n            Self::Info => theme.style.status.info_border(cx),\n            Self::Hint => theme.style.status.hint_border(cx),\n        }\n    }\n\n    pub(crate) fn highlight_style(&self, cx: &App) -> HighlightStyle {\n        let theme = &cx.theme().highlight_theme;\n\n        let color = match self {\n            Self::Error => Some(theme.style.status.error(cx)),\n            Self::Warning => Some(theme.style.status.warning(cx)),\n            Self::Info => Some(theme.style.status.info(cx)),\n            Self::Hint => Some(theme.style.status.hint(cx)),\n        };\n\n        let mut style = HighlightStyle::default();\n        style.underline = Some(UnderlineStyle {\n            color: color,\n            thickness: px(1.),\n            wavy: true,\n        });\n\n        style\n    }\n}\n\nimpl Diagnostic {\n    pub fn new(range: Range<impl Into<Position>>, message: impl Into<SharedString>) -> Self {\n        Self {\n            range: range.start.into()..range.end.into(),\n            message: message.into(),\n            ..Default::default()\n        }\n    }\n\n    pub fn with_severity(mut self, severity: impl Into<DiagnosticSeverity>) -> Self {\n        self.severity = severity.into();\n        self\n    }\n\n    pub fn with_code(mut self, code: impl Into<SharedString>) -> Self {\n        self.code = Some(code.into());\n        self\n    }\n\n    pub fn with_source(mut self, source: impl Into<SharedString>) -> Self {\n        self.source = Some(source.into());\n        self\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Default)]\npub(crate) struct DiagnosticEntry {\n    /// The byte range of the diagnostic in the rope.\n    pub range: Range<usize>,\n    pub diagnostic: Diagnostic,\n}\n\nimpl Deref for DiagnosticEntry {\n    type Target = Diagnostic;\n\n    fn deref(&self) -> &Self::Target {\n        &self.diagnostic\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub struct DiagnosticSummary {\n    count: usize,\n    start: usize,\n    end: usize,\n}\n\nimpl sum_tree::Item for DiagnosticEntry {\n    type Summary = DiagnosticSummary;\n    fn summary(&self, _cx: &()) -> Self::Summary {\n        DiagnosticSummary {\n            count: 1,\n            start: self.range.start,\n            end: self.range.end,\n        }\n    }\n}\n\nimpl sum_tree::Summary for DiagnosticSummary {\n    type Context<'a> = &'a ();\n    fn zero(_: Self::Context<'_>) -> Self {\n        DiagnosticSummary {\n            count: 0,\n            start: usize::MIN,\n            end: usize::MIN,\n        }\n    }\n\n    fn add_summary(&mut self, other: &Self, _: Self::Context<'_>) {\n        self.start = other.start;\n        self.end = other.end;\n        self.count += other.count;\n    }\n}\n\n/// For seeking by byte range.\nimpl SeekTarget<'_, DiagnosticSummary, DiagnosticSummary> for usize {\n    fn cmp(&self, other: &DiagnosticSummary, _: &()) -> Ordering {\n        if *self < other.start {\n            Ordering::Less\n        } else if *self > other.end {\n            Ordering::Greater\n        } else {\n            Ordering::Equal\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct DiagnosticSet {\n    text: Rope,\n    diagnostics: SumTree<DiagnosticEntry>,\n}\n\nimpl DiagnosticSet {\n    pub fn new(text: &Rope) -> Self {\n        Self {\n            text: text.clone(),\n            diagnostics: SumTree::new(&()),\n        }\n    }\n\n    pub fn reset(&mut self, text: &Rope) {\n        self.text = text.clone();\n        self.clear();\n    }\n\n    pub fn push(&mut self, diagnostic: impl Into<Diagnostic>) {\n        let diagnostic = diagnostic.into();\n        let start = self.text.position_to_offset(&diagnostic.range.start);\n        let end = self.text.position_to_offset(&diagnostic.range.end);\n\n        self.diagnostics.push(\n            DiagnosticEntry {\n                range: start..end,\n                diagnostic,\n            },\n            &(),\n        );\n    }\n\n    pub fn extend<D, I>(&mut self, diagnostics: D)\n    where\n        D: IntoIterator<Item = I>,\n        I: Into<Diagnostic>,\n    {\n        for diagnostic in diagnostics {\n            self.push(diagnostic.into());\n        }\n    }\n\n    pub fn len(&self) -> usize {\n        self.diagnostics.summary().count\n    }\n\n    pub fn clear(&mut self) {\n        self.diagnostics = SumTree::new(&());\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.diagnostics.is_empty()\n    }\n\n    pub(crate) fn range(&self, range: Range<usize>) -> impl Iterator<Item = &DiagnosticEntry> {\n        let mut cursor = self.diagnostics.cursor::<DiagnosticSummary>(&());\n        cursor.seek(&range.start, Bias::Left);\n        std::iter::from_fn(move || {\n            if let Some(entry) = cursor.item() {\n                if entry.range.start < range.end {\n                    cursor.next();\n                    return Some(entry);\n                }\n            }\n            None\n        })\n    }\n\n    pub(crate) fn for_offset(&self, offset: usize) -> Option<&DiagnosticEntry> {\n        self.range(offset..offset + 1).next()\n    }\n\n    pub(crate) fn styles_for_range(\n        &self,\n        range: &Range<usize>,\n        cx: &App,\n    ) -> Vec<(Range<usize>, HighlightStyle)> {\n        if self.diagnostics.is_empty() {\n            return vec![];\n        }\n\n        let mut styles = vec![];\n        for entry in self.range(range.clone()) {\n            let range = entry.range.clone();\n            styles.push((range, entry.diagnostic.severity.highlight_style(cx)));\n        }\n\n        styles\n    }\n\n    #[allow(unused)]\n    pub(crate) fn iter(&self) -> impl Iterator<Item = &DiagnosticEntry> {\n        self.diagnostics.iter()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::input::Position;\n\n    #[test]\n    fn test_diagnostic() {\n        use ropey::Rope;\n\n        use super::{Diagnostic, DiagnosticSet, DiagnosticSeverity};\n\n        let text = Rope::from(\"Hello, 你好warld!\\nThis is a test.\\nGoodbye, world!\");\n        let mut diagnostics = DiagnosticSet::new(&text);\n\n        diagnostics.push(\n            Diagnostic::new(\n                Position::new(0, 7)..Position::new(0, 17),\n                \"Spelling mistake\",\n            )\n            .with_severity(DiagnosticSeverity::Warning),\n        );\n        diagnostics.push(\n            Diagnostic::new(Position::new(2, 9)..Position::new(2, 14), \"Syntax error\")\n                .with_severity(DiagnosticSeverity::Error),\n        );\n\n        assert_eq!(diagnostics.len(), 2);\n        let items = diagnostics.iter().collect::<Vec<_>>();\n\n        assert_eq!(items[0].message.as_str(), \"Spelling mistake\");\n        assert_eq!(items[0].range, 7..19);\n\n        assert_eq!(items[1].message.as_str(), \"Syntax error\");\n        assert_eq!(items[1].range, 45..50);\n\n        let items = diagnostics.range(6..48).collect::<Vec<_>>();\n        assert_eq!(items.len(), 2);\n\n        let item = diagnostics.for_offset(10).unwrap();\n        assert_eq!(item.message.as_str(), \"Spelling mistake\");\n\n        let item = diagnostics.for_offset(30);\n        assert!(item.is_none());\n\n        let item = diagnostics.for_offset(46).unwrap();\n        assert_eq!(item.message.as_str(), \"Syntax error\");\n\n        diagnostics.push(\n            Diagnostic::new(Position::new(1, 5)..Position::new(1, 7), \"Info message\")\n                .with_severity(DiagnosticSeverity::Info),\n        );\n        assert_eq!(diagnostics.len(), 3);\n\n        diagnostics.clear();\n        assert_eq!(diagnostics.len(), 0);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/highlighter/highlighter.rs",
    "content": "use crate::highlighter::{HighlightTheme, LanguageRegistry};\n\nuse anyhow::{Context, Result, anyhow};\nuse gpui::{HighlightStyle, SharedString};\n\nuse ropey::{ChunkCursor, Rope};\nuse std::sync::Arc;\nuse std::time::{Duration, Instant};\nuse std::{\n    collections::{BTreeSet, HashMap},\n    ops::Range,\n    usize,\n};\nuse tree_sitter::{\n    InputEdit, ParseOptions, Parser, Point, Query, QueryCursor, StreamingIterator, Tree,\n};\n\n/// When a node spans more than this many bytes beyond the requested query\n/// range, we recurse into its children instead of querying it directly.\nconst LARGE_NODE_THRESHOLD: usize = 8 * 1024;\n\n/// A syntax highlighter that supports incremental parsing, multiline text,\n/// and caching of highlight results.\n#[allow(unused)]\npub struct SyntaxHighlighter {\n    language: SharedString,\n    query: Option<Query>,\n    /// A separate query for injection patterns that have `#set! injection.combined`.\n    combined_injections_query: Option<Arc<Query>>,\n    injection_queries: HashMap<SharedString, Query>,\n\n    locals_pattern_index: usize,\n    highlights_pattern_index: usize,\n    // highlight_indices: Vec<Option<Highlight>>,\n    non_local_variable_patterns: Vec<bool>,\n    injection_content_capture_index: Option<u32>,\n    injection_language_capture_index: Option<u32>,\n    combined_injection_content_capture_index: Option<u32>,\n    local_scope_capture_index: Option<u32>,\n    local_def_capture_index: Option<u32>,\n    local_def_value_capture_index: Option<u32>,\n    local_ref_capture_index: Option<u32>,\n\n    /// The last parsed source text.\n    text: Rope,\n    parser: Parser,\n    /// The last parsed tree.\n    tree: Option<Tree>,\n\n    /// Parsed injection trees (language → tree with ranges).\n    /// These are built once in update() and queried multiple times in match_styles().\n    injection_layers: HashMap<SharedString, InjectionLayer>,\n}\n\n/// A parsed injection layer.\n/// Stores the parsed tree and the ranges it covers.\npub(crate) struct InjectionLayer {\n    pub(crate) tree: Tree,\n}\n\n/// Data needed to compute injection layers on a background thread.\npub(crate) struct InjectionParseData {\n    pub(crate) query: Arc<Query>,\n    pub(crate) content_capture_index: Option<u32>,\n    /// Old injection trees for incremental re-parsing.\n    pub(crate) old_layers: HashMap<SharedString, Tree>,\n}\n\nstruct TextProvider<'a>(&'a Rope);\nstruct ByteChunks<'a> {\n    cursor: ChunkCursor<'a>,\n    node_start: usize,\n    node_end: usize,\n    at_first: bool,\n}\nimpl<'a> tree_sitter::TextProvider<&'a [u8]> for TextProvider<'a> {\n    type I = ByteChunks<'a>;\n\n    fn text(&mut self, node: tree_sitter::Node) -> Self::I {\n        let range = node.byte_range();\n        let cursor = self.0.chunk_cursor_at(range.start);\n\n        ByteChunks {\n            cursor,\n            node_start: range.start,\n            node_end: range.end,\n            at_first: true,\n        }\n    }\n}\n\nimpl<'a> Iterator for ByteChunks<'a> {\n    type Item = &'a [u8];\n\n    fn next(&mut self) -> Option<Self::Item> {\n        if !self.at_first {\n            if !self.cursor.next() {\n                return None;\n            }\n        }\n        self.at_first = false;\n\n        let chunk_byte_start = self.cursor.byte_offset();\n        if chunk_byte_start >= self.node_end {\n            return None;\n        }\n\n        let chunk = self.cursor.chunk().as_bytes();\n\n        // Slice the chunk to only include bytes within the node's range.\n        let start_in_chunk = self.node_start.saturating_sub(chunk_byte_start);\n        let end_in_chunk = (self.node_end - chunk_byte_start).min(chunk.len());\n\n        if start_in_chunk >= end_in_chunk {\n            return None;\n        }\n\n        Some(&chunk[start_in_chunk..end_in_chunk])\n    }\n}\n\n#[derive(Debug, Default, Clone)]\nstruct HighlightSummary {\n    count: usize,\n    start: usize,\n    end: usize,\n    min_start: usize,\n    max_end: usize,\n}\n\n/// The highlight item, the range is offset of the token in the tree.\n#[derive(Debug, Default, Clone)]\nstruct HighlightItem {\n    /// The byte range of the highlight in the text.\n    range: Range<usize>,\n    /// The highlight name, like `function`, `string`, `comment`, etc.\n    name: SharedString,\n}\n\nimpl HighlightItem {\n    pub fn new(range: Range<usize>, name: impl Into<SharedString>) -> Self {\n        Self {\n            range,\n            name: name.into(),\n        }\n    }\n}\n\nimpl sum_tree::Item for HighlightItem {\n    type Summary = HighlightSummary;\n    fn summary(&self, _cx: &()) -> Self::Summary {\n        HighlightSummary {\n            count: 1,\n            start: self.range.start,\n            end: self.range.end,\n            min_start: self.range.start,\n            max_end: self.range.end,\n        }\n    }\n}\n\nimpl sum_tree::Summary for HighlightSummary {\n    type Context<'a> = &'a ();\n    fn zero(_: Self::Context<'_>) -> Self {\n        HighlightSummary {\n            count: 0,\n            start: usize::MIN,\n            end: usize::MAX,\n            min_start: usize::MAX,\n            max_end: usize::MIN,\n        }\n    }\n\n    fn add_summary(&mut self, other: &Self, _: Self::Context<'_>) {\n        self.min_start = self.min_start.min(other.min_start);\n        self.max_end = self.max_end.max(other.max_end);\n        self.start = other.start;\n        self.end = other.end;\n        self.count += other.count;\n    }\n}\n\nimpl<'a> sum_tree::Dimension<'a, HighlightSummary> for usize {\n    fn zero(_: &()) -> Self {\n        0\n    }\n\n    fn add_summary(&mut self, _: &'a HighlightSummary, _: &()) {}\n}\n\nimpl<'a> sum_tree::Dimension<'a, HighlightSummary> for Range<usize> {\n    fn zero(_: &()) -> Self {\n        Default::default()\n    }\n\n    fn add_summary(&mut self, summary: &'a HighlightSummary, _: &()) {\n        self.start = summary.start;\n        self.end = summary.end;\n    }\n}\n\nimpl SyntaxHighlighter {\n    /// Create a new SyntaxHighlighter for HTML.\n    pub fn new(lang: &str) -> Self {\n        match Self::build_combined_injections_query(&lang) {\n            Ok(result) => result,\n            Err(err) => {\n                tracing::warn!(\n                    \"SyntaxHighlighter init failed, fallback to use `text`, {}\",\n                    err\n                );\n                Self::build_combined_injections_query(\"text\").unwrap()\n            }\n        }\n    }\n\n    /// Build the combined injections query for the given language.\n    ///\n    /// https://github.com/tree-sitter/tree-sitter/blob/v0.25.5/highlight/src/lib.rs#L336\n    fn build_combined_injections_query(lang: &str) -> Result<Self> {\n        let Some(config) = LanguageRegistry::singleton().language(&lang) else {\n            return Err(anyhow!(\n                \"language {:?} is not registered in `LanguageRegistry`\",\n                lang\n            ));\n        };\n\n        let mut parser = Parser::new();\n        parser\n            .set_language(&config.language)\n            .context(\"parse set_language\")?;\n\n        // Concatenate the query strings, keeping track of the start offset of each section.\n        let mut query_source = String::new();\n        query_source.push_str(&config.injections);\n        let locals_query_offset = query_source.len();\n        query_source.push_str(&config.locals);\n        let highlights_query_offset = query_source.len();\n        query_source.push_str(&config.highlights);\n\n        // Construct a single query by concatenating the three query strings, but record the\n        // range of pattern indices that belong to each individual string.\n        let mut query = Query::new(&config.language, &query_source).context(\"new query\")?;\n\n        let mut locals_pattern_index = 0;\n        let mut highlights_pattern_index = 0;\n        for i in 0..(query.pattern_count()) {\n            let pattern_offset = query.start_byte_for_pattern(i);\n            if pattern_offset < highlights_query_offset {\n                if pattern_offset < highlights_query_offset {\n                    highlights_pattern_index += 1;\n                }\n                if pattern_offset < locals_query_offset {\n                    locals_pattern_index += 1;\n                }\n            }\n        }\n\n        // Separate combined injection patterns into their own query.\n        // Combined injections (e.g., PHP's HTML text nodes) collect all matching\n        // ranges and parse them as a single document, so that opening/closing\n        // tags across injection boundaries are correctly matched.\n        let combined_injections_query = if !config.injections.is_empty() {\n            if let Ok(mut ciq) = Query::new(&config.language, &config.injections) {\n                let mut has_combined_query = false;\n                for pattern_index in 0..locals_pattern_index {\n                    let settings = query.property_settings(pattern_index);\n                    if settings.iter().any(|s| &*s.key == \"injection.combined\") {\n                        has_combined_query = true;\n                        query.disable_pattern(pattern_index);\n                    } else {\n                        ciq.disable_pattern(pattern_index);\n                    }\n                }\n                if has_combined_query { Some(Arc::new(ciq)) } else { None }\n            } else {\n                None\n            }\n        } else {\n            None\n        };\n\n        let combined_injection_content_capture_index =\n            combined_injections_query.as_ref().and_then(|q| {\n                q.capture_names()\n                    .iter()\n                    .position(|name| *name == \"injection.content\")\n                    .map(|i| i as u32)\n            });\n\n        // Find all of the highlighting patterns that are disabled for nodes that\n        // have been identified as local variables.\n        let non_local_variable_patterns = (0..query.pattern_count())\n            .map(|i| {\n                query\n                    .property_predicates(i)\n                    .iter()\n                    .any(|(prop, positive)| !*positive && prop.key.as_ref() == \"local\")\n            })\n            .collect();\n\n        // Store the numeric ids for all of the special captures.\n        let mut injection_content_capture_index = None;\n        let mut injection_language_capture_index = None;\n        let mut local_def_capture_index = None;\n        let mut local_def_value_capture_index = None;\n        let mut local_ref_capture_index = None;\n        let mut local_scope_capture_index = None;\n        for (i, name) in query.capture_names().iter().enumerate() {\n            let i = Some(i as u32);\n            match *name {\n                \"injection.content\" => injection_content_capture_index = i,\n                \"injection.language\" => injection_language_capture_index = i,\n                \"local.definition\" => local_def_capture_index = i,\n                \"local.definition-value\" => local_def_value_capture_index = i,\n                \"local.reference\" => local_ref_capture_index = i,\n                \"local.scope\" => local_scope_capture_index = i,\n                _ => {}\n            }\n        }\n\n        let mut injection_queries = HashMap::new();\n        for inj_language in config.injection_languages.iter() {\n            if let Some(inj_config) = LanguageRegistry::singleton().language(&inj_language) {\n                match Query::new(&inj_config.language, &inj_config.highlights) {\n                    Ok(q) => {\n                        injection_queries.insert(inj_config.name.clone(), q);\n                    }\n                    Err(e) => {\n                        tracing::error!(\n                            \"failed to build injection query for {:?}: {:?}\",\n                            inj_config.name,\n                            e\n                        );\n                    }\n                }\n            }\n        }\n\n        // let highlight_indices = vec![None; query.capture_names().len()];\n\n        Ok(Self {\n            language: config.name.clone(),\n            query: Some(query),\n            combined_injections_query,\n            injection_queries,\n\n            locals_pattern_index,\n            highlights_pattern_index,\n            non_local_variable_patterns,\n            injection_content_capture_index,\n            injection_language_capture_index,\n            combined_injection_content_capture_index,\n            local_scope_capture_index,\n            local_def_capture_index,\n            local_def_value_capture_index,\n            local_ref_capture_index,\n            text: Rope::new(),\n            parser,\n            tree: None,\n            injection_layers: HashMap::new(),\n        })\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.text.len() == 0\n    }\n\n    /// Get the parsed tree (if available)\n    pub fn tree(&self) -> Option<&Tree> {\n        self.tree.as_ref()\n    }\n\n    /// Returns the language name for this highlighter.\n    pub fn language(&self) -> &SharedString {\n        &self.language\n    }\n\n    /// Returns a reference to the current text.\n    pub fn text(&self) -> &Rope {\n        &self.text\n    }\n\n    /// Highlight the given text, returning a map from byte ranges to highlight captures.\n    ///\n    /// Uses incremental parsing by `edit` to efficiently update the highlighter's state.\n    /// When `timeout` is `Some`, aborts if parsing exceeds the given duration\n    /// and returns `false`. On timeout the old tree is preserved so highlighting\n    /// still works with stale data, but `self.text` is updated so that the\n    /// caller can send the current text to a background parse.\n    /// When `timeout` is `None`, parsing runs to completion and always returns `true`.\n    pub fn update(\n        &mut self,\n        edit: Option<InputEdit>,\n        text: &Rope,\n        timeout: Option<Duration>,\n    ) -> bool {\n        if self.text.eq(text) {\n            return true;\n        }\n\n        let edit = edit.unwrap_or(InputEdit {\n            start_byte: 0,\n            old_end_byte: 0,\n            new_end_byte: text.len(),\n            start_position: Point::new(0, 0),\n            old_end_position: Point::new(0, 0),\n            new_end_position: Point::new(0, 0),\n        });\n\n        let mut old_tree = self\n            .tree\n            .take()\n            .unwrap_or(self.parser.parse(\"\", None).unwrap());\n        old_tree.edit(&edit);\n\n        let mut timed_out = false;\n        let start = Instant::now();\n        let mut progress = |_: &tree_sitter::ParseState| -> bool {\n            let Some(budget) = timeout else {\n                return false;\n            };\n\n            if start.elapsed() > budget {\n                timed_out = true;\n                return true; // Cancel execution\n            }\n\n            false\n        };\n\n        let options = ParseOptions::new().progress_callback(&mut progress);\n        let new_tree = self.parser.parse_with_options(\n            &mut move |offset, _| {\n                if offset >= text.len() {\n                    \"\"\n                } else {\n                    let (chunk, chunk_byte_ix) = text.chunk(offset);\n                    &chunk[offset - chunk_byte_ix..]\n                }\n            },\n            Some(&old_tree),\n            Some(options),\n        );\n\n        if timed_out || new_tree.is_none() {\n            // Restore the old tree so highlighting continues with stale data.\n            self.tree = Some(old_tree);\n            self.text = text.clone();\n            return false;\n        }\n\n        let new_tree = new_tree.unwrap();\n        self.tree = Some(new_tree.clone());\n        self.text = text.clone();\n        self.parse_combined_injections(&new_tree);\n        true\n    }\n\n    /// Returns the data needed to compute injection layers on a background thread.\n    /// Returns `None` if this language has no combined injections.\n    pub(crate) fn injection_parse_data(&self) -> Option<InjectionParseData> {\n        let query = self.combined_injections_query.clone()?;\n        Some(InjectionParseData {\n            query,\n            content_capture_index: self.combined_injection_content_capture_index,\n            old_layers: self\n                .injection_layers\n                .iter()\n                .map(|(k, v)| (k.clone(), v.tree.clone()))\n                .collect(),\n        })\n    }\n\n    /// Compute injection layers from a freshly-parsed main tree.\n    /// This is pure computation with no side effects and is safe to run on a\n    /// background thread.\n    pub(crate) fn compute_injection_layers(\n        data: InjectionParseData,\n        tree: &Tree,\n        text: &Rope,\n    ) -> HashMap<SharedString, InjectionLayer> {\n        let root_node = tree.root_node();\n        let mut cursor = QueryCursor::new();\n        let mut matches = cursor.matches(&data.query, root_node, TextProvider(text));\n\n        let mut combined_ranges: HashMap<SharedString, Vec<tree_sitter::Range>> = HashMap::new();\n        while let Some(query_match) = matches.next() {\n            let mut language_name: Option<SharedString> = None;\n            if let Some(prop) = data\n                .query\n                .property_settings(query_match.pattern_index)\n                .iter()\n                .find(|prop| prop.key.as_ref() == \"injection.language\")\n            {\n                language_name = prop\n                    .value\n                    .as_ref()\n                    .map(|v| SharedString::from(v.to_string()));\n            }\n            let Some(language_name) = language_name else {\n                continue;\n            };\n            for capture in query_match\n                .captures\n                .iter()\n                .filter(|cap| Some(cap.index) == data.content_capture_index)\n            {\n                combined_ranges\n                    .entry(language_name.clone())\n                    .or_default()\n                    .push(capture.node.range());\n            }\n        }\n\n        let mut new_layers = HashMap::new();\n        for (language_name, ranges) in combined_ranges {\n            if ranges.is_empty() {\n                continue;\n            }\n            let Some(config) = LanguageRegistry::singleton().language(&language_name) else {\n                continue;\n            };\n            let mut parser = Parser::new();\n            if parser.set_language(&config.language).is_err() {\n                continue;\n            }\n            if parser.set_included_ranges(&ranges).is_err() {\n                continue;\n            }\n            let old_tree = data.old_layers.get(&language_name);\n            let Some(new_tree) = parser.parse_with_options(\n                &mut |offset, _| {\n                    if offset >= text.len() {\n                        \"\"\n                    } else {\n                        let (chunk, chunk_byte_ix) = text.chunk(offset);\n                        &chunk[offset - chunk_byte_ix..]\n                    }\n                },\n                old_tree,\n                None,\n            ) else {\n                continue;\n            };\n            new_layers.insert(language_name, InjectionLayer { tree: new_tree });\n        }\n        new_layers\n    }\n\n    /// Apply a tree that was parsed on a background thread.\n    ///\n    /// `injection_layers` must also be pre-computed in the background via\n    /// [`compute_injection_layers`] to avoid blocking the main thread.\n    pub(crate) fn apply_background_tree(\n        &mut self,\n        tree: Tree,\n        text: &Rope,\n        injection_layers: HashMap<SharedString, InjectionLayer>,\n    ) {\n        // Only apply if the text still matches what was parsed.\n        if !self.text.eq(text) {\n            return;\n        }\n\n        self.tree = Some(tree);\n        self.injection_layers = injection_layers;\n    }\n\n    /// Parse all combined injections after main tree is updated.\n    /// pattern: parse once in update, query many times in render.\n    /// Parse all combined injections after main tree is updated.\n    /// pattern: parse once in update, query many times in render.\n    fn parse_combined_injections(&mut self, tree: &Tree) {\n        let Some(data) = self.injection_parse_data() else {\n            return;\n        };\n        self.injection_layers = Self::compute_injection_layers(data, tree, &self.text.clone());\n    }\n\n    /// Match the visible ranges of nodes in the Tree for highlighting.\n    fn match_styles(&self, range: Range<usize>) -> Vec<HighlightItem> {\n        let mut highlights = vec![];\n        let Some(tree) = &self.tree else {\n            return highlights;\n        };\n\n        let Some(query) = &self.query else {\n            return highlights;\n        };\n\n        let root_node = tree.root_node();\n        let source = &self.text;\n\n        // Query pre-parsed injection layers.\n        for (language_name, layer) in &self.injection_layers {\n            let Some(query) = self.injection_queries.get(language_name) else {\n                continue;\n            };\n\n            let mut query_cursor = QueryCursor::new();\n            query_cursor.set_byte_range(range.clone());\n\n            let mut matches =\n                query_cursor.matches(query, layer.tree.root_node(), TextProvider(&self.text));\n\n            let mut last_end = 0usize;\n            while let Some(m) = matches.next() {\n                for cap in m.captures {\n                    let node_range = cap.node.start_byte()..cap.node.end_byte();\n\n                    if node_range.start < last_end {\n                        continue;\n                    }\n\n                    if let Some(highlight_name) = query.capture_names().get(cap.index as usize) {\n                        last_end = node_range.end;\n                        highlights.push(HighlightItem::new(\n                            node_range,\n                            SharedString::from(highlight_name.to_string()),\n                        ));\n                    }\n                }\n            }\n        }\n\n        let query_nodes = collect_query_nodes(root_node, &range);\n\n        for query_node in &query_nodes {\n            let mut query_cursor = QueryCursor::new();\n            query_cursor.set_byte_range(range.clone());\n\n            let mut matches = query_cursor.matches(&query, *query_node, TextProvider(&source));\n\n            while let Some(query_match) = matches.next() {\n                for cap in query_match.captures {\n                    let node = cap.node;\n\n                    let Some(highlight_name) = query.capture_names().get(cap.index as usize) else {\n                        continue;\n                    };\n\n                    let node_range: Range<usize> = node.start_byte()..node.end_byte();\n                    let highlight_name = SharedString::from(highlight_name.to_string());\n\n                    // Merge near range and same highlight name\n                    let last_item = highlights.last();\n                    let last_range = last_item.map(|item| &item.range).unwrap_or(&(0..0));\n                    let last_highlight_name = last_item.map(|item| item.name.clone());\n\n                    if last_range == &node_range {\n                        // case:\n                        // last_range: 213..220, last_highlight_name: Some(\"property\")\n                        // last_range: 213..220, last_highlight_name: Some(\"string\")\n                        highlights.push(HighlightItem::new(\n                            node_range,\n                            last_highlight_name.unwrap_or(highlight_name),\n                        ));\n                    } else {\n                        highlights.push(HighlightItem::new(node_range, highlight_name.clone()));\n                    }\n                }\n            }\n        }\n\n        // DO NOT REMOVE THIS PRINT, it's useful for debugging\n        // for item in highlights {\n        //     println!(\"item: {:?}\", item);\n        // }\n\n        highlights\n    }\n\n    /// Returns the syntax highlight styles for a range of text.\n    ///\n    /// The argument `range` is the range of bytes in the text to highlight.\n    ///\n    /// Returns a vector of tuples where each tuple contains:\n    /// - A byte range relative to the text\n    /// - The corresponding highlight style for that range\n    ///\n    /// # Example\n    ///\n    /// ```no_run\n    /// use gpui_component::highlighter::{HighlightTheme, SyntaxHighlighter};\n    /// use ropey::Rope;\n    ///\n    /// let code = \"fn main() {\\n    println!(\\\"Hello\\\");\\n}\";\n    /// let rope = Rope::from_str(code);\n    /// let mut highlighter = SyntaxHighlighter::new(\"rust\");\n    /// highlighter.update(None, &rope, None);\n    ///\n    /// let theme = HighlightTheme::default_dark();\n    /// let range = 0..code.len();\n    /// let styles = highlighter.styles(&range, &theme);\n    /// ```\n    pub fn styles(\n        &self,\n        range: &Range<usize>,\n        theme: &HighlightTheme,\n    ) -> Vec<(Range<usize>, HighlightStyle)> {\n        let mut styles = vec![];\n        let start_offset = range.start;\n\n        let highlights = self.match_styles(range.clone());\n\n        // let mut iter_count = 0;\n        for item in highlights {\n            // iter_count += 1;\n            let node_range = &item.range;\n            let name = &item.name;\n\n            // Avoid start larger than end\n            let mut node_range = node_range.start.max(range.start)..node_range.end.min(range.end);\n            if node_range.start > node_range.end {\n                node_range.end = node_range.start;\n            }\n\n            styles.push((node_range, theme.style(name.as_ref()).unwrap_or_default()));\n        }\n\n        // If the matched styles is empty, return a default range.\n        if styles.len() == 0 {\n            return vec![(start_offset..range.end, HighlightStyle::default())];\n        }\n\n        let styles = unique_styles(&range, styles);\n\n        // NOTE: DO NOT remove this comment, it is used for debugging.\n        // for style in &styles {\n        //     println!(\"---- style: {:?} - {:?}\", style.0, style.1.color);\n        // }\n        // println!(\"--------------------------------\");\n\n        styles\n    }\n}\n\n/// To merge intersection ranges, let the subsequent range cover\n/// the previous overlapping range and split the previous range.\n///\n/// From:\n///\n/// AA\n///   BBB\n///    CCCCC\n///      DD\n///         EEEE\n///\n/// To:\n///\n/// AABCCDDCEEEE\npub(crate) fn unique_styles(\n    total_range: &Range<usize>,\n    styles: Vec<(Range<usize>, HighlightStyle)>,\n) -> Vec<(Range<usize>, HighlightStyle)> {\n    if styles.is_empty() {\n        return styles;\n    }\n\n    // Create intervals: (position, is_start, style_index)\n    let mut intervals: Vec<(usize, bool, usize)> = Vec::with_capacity(styles.len() * 2 + 2);\n    for (i, (range, _)) in styles.iter().enumerate() {\n        intervals.push((range.start, true, i));\n        intervals.push((range.end, false, i));\n    }\n\n    intervals.push((total_range.start, true, usize::MAX));\n    intervals.push((total_range.end, false, usize::MAX));\n\n    // Sort by position, with ends before starts at same position\n    // This ensures we close ranges before opening new ones at the same position\n    intervals.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.cmp(&b.1)));\n\n    // Track significant intervals (where style ranges end) for merging decisions\n    let mut significant_intervals: BTreeSet<usize> = BTreeSet::new();\n    for (range, _) in &styles {\n        significant_intervals.insert(range.end);\n    }\n\n    let mut result: Vec<(Range<usize>, HighlightStyle)> = Vec::new();\n    let mut active_styles: Vec<usize> = Vec::new();\n    let mut last_pos = total_range.start;\n\n    for (pos, is_start, style_idx) in intervals {\n        // Skip total_range boundaries in active set management\n        let is_boundary = style_idx == usize::MAX;\n\n        if pos > last_pos {\n            let interval = last_pos..pos;\n            let combined_style = if active_styles.is_empty() {\n                HighlightStyle::default()\n            } else {\n                let mut combined = HighlightStyle::default();\n                for &idx in &active_styles {\n                    merge_highlight_style(&mut combined, &styles[idx].1);\n                }\n                combined\n            };\n            result.push((interval, combined_style));\n        }\n\n        if !is_boundary {\n            if is_start {\n                active_styles.push(style_idx);\n            } else {\n                active_styles.retain(|&i| i != style_idx);\n            }\n        }\n\n        last_pos = pos;\n    }\n\n    // Merge adjacent ranges with the same style, but not across significant boundaries\n    let mut merged: Vec<(Range<usize>, HighlightStyle)> = Vec::with_capacity(result.len());\n    for (range, style) in result {\n        if let Some((last_range, last_style)) = merged.last_mut() {\n            if last_range.end == range.start\n                && *last_style == style\n                && !significant_intervals.contains(&range.start)\n            {\n                // Merge adjacent ranges with same style, but not across significant boundaries\n                last_range.end = range.end;\n                continue;\n            }\n        }\n        merged.push((range, style));\n    }\n\n    merged\n}\n\n/// Walk the tree and collect nodes suitable for querying, skipping subtrees\n/// that fall entirely outside the byte range. Nodes much larger than the\n/// query range are recursed into so that `QueryCursor` only visits the\n/// relevant portion of the tree.\nfn collect_query_nodes<'a>(\n    root: tree_sitter::Node<'a>,\n    range: &Range<usize>,\n) -> Vec<tree_sitter::Node<'a>> {\n    let mut nodes = Vec::new();\n    collect_query_nodes_inner(root, range, &mut nodes);\n    if nodes.is_empty() {\n        nodes.push(root);\n    }\n    nodes\n}\n\nfn collect_query_nodes_inner<'a>(\n    node: tree_sitter::Node<'a>,\n    range: &Range<usize>,\n    out: &mut Vec<tree_sitter::Node<'a>>,\n) {\n    // Skip nodes entirely outside the range.\n    if node.end_byte() <= range.start || node.start_byte() >= range.end {\n        return;\n    }\n\n    let node_span = node.end_byte() - node.start_byte();\n    let range_span = range.end - range.start;\n\n    // Use `goto_first_child_for_byte` to seek directly to the first\n    // overlapping child instead of iterating all children from the start.\n    if node_span > range_span + LARGE_NODE_THRESHOLD && node.child_count() > 0 {\n        let mut cursor = node.walk();\n        if cursor.goto_first_child_for_byte(range.start).is_some() {\n            loop {\n                let child = cursor.node();\n                if child.start_byte() >= range.end {\n                    break;\n                }\n                collect_query_nodes_inner(child, range, out);\n                if !cursor.goto_next_sibling() {\n                    break;\n                }\n            }\n        }\n        return;\n    }\n\n    out.push(node);\n}\n\n/// Merge other style (Other on top)\nfn merge_highlight_style(style: &mut HighlightStyle, other: &HighlightStyle) {\n    if let Some(color) = other.color {\n        style.color = Some(color);\n    }\n    if let Some(font_weight) = other.font_weight {\n        style.font_weight = Some(font_weight);\n    }\n    if let Some(font_style) = other.font_style {\n        style.font_style = Some(font_style);\n    }\n    if let Some(background_color) = other.background_color {\n        style.background_color = Some(background_color);\n    }\n    if let Some(underline) = other.underline {\n        style.underline = Some(underline);\n    }\n    if let Some(strikethrough) = other.strikethrough {\n        style.strikethrough = Some(strikethrough);\n    }\n    if let Some(fade_out) = other.fade_out {\n        style.fade_out = Some(fade_out);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use gpui::Hsla;\n\n    use super::*;\n    use crate::Colorize as _;\n\n    fn color_style(color: Hsla) -> HighlightStyle {\n        let mut style = HighlightStyle::default();\n        style.color = Some(color);\n        style\n    }\n\n    #[track_caller]\n    fn assert_unique_styles(\n        range: Range<usize>,\n        left: Vec<(Range<usize>, HighlightStyle)>,\n        right: Vec<(Range<usize>, HighlightStyle)>,\n    ) {\n        fn color_name(c: Option<Hsla>) -> String {\n            match c {\n                Some(c) => {\n                    if c == gpui::red() {\n                        \"red\".to_string()\n                    } else if c == gpui::green() {\n                        \"green\".to_string()\n                    } else if c == gpui::blue() {\n                        \"blue\".to_string()\n                    } else {\n                        c.to_hex()\n                    }\n                }\n                None => \"clean\".to_string(),\n            }\n        }\n\n        let left = unique_styles(&range, left);\n        if left.len() != right.len() {\n            println!(\"\\n---------------------------------------------\");\n            for (range, style) in left.iter() {\n                println!(\"({:?}, {})\", range, color_name(style.color));\n            }\n            println!(\"---------------------------------------------\");\n            panic!(\"left {} styles, right {} styles\", left.len(), right.len());\n        }\n        for (left, right) in left.into_iter().zip(right) {\n            if left.1.color != right.1.color || left.0 != right.0 {\n                panic!(\n                    \"\\n left: ({:?}, {})\\nright: ({:?}, {})\\n\",\n                    left.0,\n                    color_name(left.1.color),\n                    right.0,\n                    color_name(right.1.color)\n                );\n            }\n        }\n    }\n\n    #[test]\n    #[cfg(feature = \"tree-sitter-languages\")]\n    fn test_php_combined_injection_closing_tags() {\n        let php_code = r#\"<?php\n$x = 1;\n?>\n<html>\n<body>\n  <h1><?php echo \"Hello\"; ?></h1>\n  <ul>\n    <?php foreach ($items as $item): ?>\n      <li><?php echo $item; ?></li>\n    <?php endforeach; ?>\n  </ul>\n</body>\n</html>\n\"#;\n\n        let rope = Rope::from_str(php_code);\n        let mut highlighter = SyntaxHighlighter::new(\"php\");\n        highlighter.update(None, &rope, None);\n\n        assert!(\n            highlighter.combined_injections_query.is_some(),\n            \"PHP should have combined injections query\"\n        );\n\n        let full_range = 0..php_code.len();\n        let highlights = highlighter.match_styles(full_range);\n\n        // Verify all closing HTML tags are highlighted\n        let closing_tags = [\"</h1>\", \"</li>\", \"</ul>\", \"</body>\", \"</html>\"];\n        for tag in closing_tags {\n            let pos = php_code.find(tag).unwrap();\n            let tag_name_start = pos + 2; // after \"</\"\n            let tag_name_end = tag_name_start + tag.len() - 3; // before \">\"\n\n            let has_highlight = highlights\n                .iter()\n                .any(|item| item.range.start <= tag_name_start && item.range.end >= tag_name_end);\n\n            assert!(\n                has_highlight,\n                \"closing tag {} at byte {} should be highlighted\",\n                tag, pos\n            );\n        }\n    }\n\n    #[test]\n    fn test_unique_styles() {\n        let red = color_style(gpui::red());\n        let green = color_style(gpui::green());\n        let blue = color_style(gpui::blue());\n        let clean = HighlightStyle::default();\n\n        assert_unique_styles(\n            0..65,\n            vec![\n                (2..10, clean),\n                (2..10, clean),\n                (5..11, red),\n                (2..6, clean),\n                (10..15, green),\n                (15..30, clean),\n                (29..35, blue),\n                (35..40, green),\n                (45..60, blue),\n            ],\n            vec![\n                (0..5, clean),\n                (5..6, red),\n                (6..10, red),\n                (10..11, green),\n                (11..15, green),\n                (15..29, clean),\n                (29..30, blue),\n                (30..35, blue),\n                (35..40, green),\n                (40..45, clean),\n                (45..60, blue),\n                (60..65, clean),\n            ],\n        );\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/highlighter/languages/go/highlights.scm",
    "content": "; Function calls\n\n(call_expression\n  function: (identifier) @function)\n\n(call_expression\n  function: (identifier) @function.builtin\n  (#match? @function.builtin \"^(append|cap|close|complex|copy|delete|imag|len|make|new|panic|print|println|real|recover)$\"))\n\n(call_expression\n  function: (selector_expression\n    field: (field_identifier) @function.method))\n\n; Function definitions\n\n(function_declaration\n  name: (identifier) @function)\n\n(method_declaration\n  name: (field_identifier) @function.method)\n(method_elem\n  name: (field_identifier) @function.method)\n\n; Identifiers\n(keyed_element\n  .\n  (literal_element\n    (identifier) @variable.member))\n\n(type_identifier) @type\n(field_identifier) @variable.member\n(identifier) @variable\n(package_identifier) @namespace\n\n; Operators\n\n[\n  \"--\"\n  \"-\"\n  \"-=\"\n  \":=\"\n  \"!\"\n  \"!=\"\n  \"...\"\n  \"*\"\n  \"*\"\n  \"*=\"\n  \"/\"\n  \"/=\"\n  \"&\"\n  \"&&\"\n  \"&=\"\n  \"%\"\n  \"%=\"\n  \"^\"\n  \"^=\"\n  \"+\"\n  \"++\"\n  \"+=\"\n  \"<-\"\n  \"<\"\n  \"<<\"\n  \"<<=\"\n  \"<=\"\n  \"=\"\n  \"==\"\n  \">\"\n  \">=\"\n  \">>\"\n  \">>=\"\n  \"|\"\n  \"|=\"\n  \"||\"\n  \"~\"\n] @operator\n\n; Keywords\n\n[\n  \"break\"\n  \"case\"\n  \"chan\"\n  \"const\"\n  \"continue\"\n  \"default\"\n  \"defer\"\n  \"else\"\n  \"fallthrough\"\n  \"for\"\n  \"func\"\n  \"go\"\n  \"goto\"\n  \"if\"\n  \"import\"\n  \"interface\"\n  \"map\"\n  \"package\"\n  \"range\"\n  \"return\"\n  \"select\"\n  \"struct\"\n  \"switch\"\n  \"type\"\n  \"var\"\n] @keyword\n\n; Literals\n\n[\n  (interpreted_string_literal)\n  (raw_string_literal)\n  (rune_literal)\n] @string\n\n(escape_sequence) @string.escape\n\n[\n  (int_literal)\n  (float_literal)\n  (imaginary_literal)\n] @number\n\n(const_spec\n  name: (identifier) @constant)\n\n[\n  (true)\n  (false)\n] @boolean\n\n[\n  (nil)\n  (iota)\n] @constant.builtin\n\n(comment) @comment\n"
  },
  {
    "path": "crates/ui/src/highlighter/languages/html/highlights.scm",
    "content": "(tag_name) @tag\n(erroneous_end_tag_name) @tag.error\n(doctype) @constant\n(attribute_name) @attribute\n(attribute_value) @string\n(comment) @comment\n\n[\n  \"<\"\n  \">\"\n  \"</\"\n  \"/>\"\n] @punctuation.bracket"
  },
  {
    "path": "crates/ui/src/highlighter/languages/html/injections.scm",
    "content": "((script_element\n  (raw_text) @injection.content)\n (#set! injection.language \"javascript\"))\n\n((style_element\n  (raw_text) @injection.content)\n (#set! injection.language \"css\"))"
  },
  {
    "path": "crates/ui/src/highlighter/languages/javascript/highlights.scm",
    "content": "; Variables\n;----------\n\n(identifier) @variable\n\n; Properties\n;-----------\n\n(property_identifier) @property\n(shorthand_property_identifier) @property\n(shorthand_property_identifier_pattern) @property\n(private_property_identifier) @property\n\n; Function and method definitions\n;--------------------------------\n\n(function_expression\n  name: (identifier) @function)\n(function_declaration\n  name: (identifier) @function)\n(method_definition\n  name: (property_identifier) @function.method)\n\n(pair\n  key: (property_identifier) @function.method\n  value: [(function_expression) (arrow_function)])\n\n(assignment_expression\n  left: (member_expression\n    property: (property_identifier) @function.method)\n  right: [(function_expression) (arrow_function)])\n\n(variable_declarator\n  name: (identifier) @function\n  value: [(function_expression) (arrow_function)])\n\n(assignment_expression\n  left: (identifier) @function\n  right: [(function_expression) (arrow_function)])\n\n; Function and method calls\n;--------------------------\n\n(call_expression\n  function: (identifier) @function)\n\n(call_expression\n  function: (member_expression\n    property: (property_identifier) @function.method))\n\n; Special identifiers\n;--------------------\n\n((identifier) @type\n (#match? @type \"^[A-Z]\"))\n\n([\n  (identifier)\n  (shorthand_property_identifier)\n  (shorthand_property_identifier_pattern)\n ] @constant\n (#match? @constant \"^_*[A-Z_][A-Z\\\\d_]*$\"))\n\n((identifier) @variable.builtin\n (#match? @variable.builtin \"^(arguments|module|console|window|document)$\")\n (#is-not? local))\n\n((identifier) @function.builtin\n (#eq? @function.builtin \"require\")\n (#is-not? local))\n\n; Literals\n;---------\n\n(this) @variable.builtin\n(super) @variable.builtin\n\n[\n  (true)\n  (false)\n  (null)\n  (undefined)\n] @constant.builtin\n\n(comment) @comment\n\n[\n  (string)\n  (template_string)\n] @string\n\n(regex) @string.special\n(number) @number\n\n; Tokens\n;-------\n\n[\n  \";\"\n  (optional_chain)\n  \".\"\n  \",\"\n] @punctuation.delimiter\n\n[\n  \"-\"\n  \"--\"\n  \"-=\"\n  \"+\"\n  \"++\"\n  \"+=\"\n  \"*\"\n  \"*=\"\n  \"**\"\n  \"**=\"\n  \"/\"\n  \"/=\"\n  \"%\"\n  \"%=\"\n  \"<\"\n  \"<=\"\n  \"<<\"\n  \"<<=\"\n  \"=\"\n  \"==\"\n  \"===\"\n  \"!\"\n  \"!=\"\n  \"!==\"\n  \"=>\"\n  \">\"\n  \">=\"\n  \">>\"\n  \">>=\"\n  \">>>\"\n  \">>>=\"\n  \"~\"\n  \"^\"\n  \"&\"\n  \"|\"\n  \"^=\"\n  \"&=\"\n  \"|=\"\n  \"&&\"\n  \"||\"\n  \"??\"\n  \"&&=\"\n  \"||=\"\n  \"??=\"\n] @operator\n\n[\n  \"(\"\n  \")\"\n  \"[\"\n  \"]\"\n  \"{\"\n  \"}\"\n]  @punctuation.bracket\n\n(template_substitution\n  \"${\" @punctuation.special\n  \"}\" @punctuation.special) @embedded\n\n[\n  \"as\"\n  \"async\"\n  \"await\"\n  \"break\"\n  \"case\"\n  \"catch\"\n  \"class\"\n  \"const\"\n  \"continue\"\n  \"debugger\"\n  \"default\"\n  \"delete\"\n  \"do\"\n  \"else\"\n  \"export\"\n  \"extends\"\n  \"finally\"\n  \"for\"\n  \"from\"\n  \"function\"\n  \"get\"\n  \"if\"\n  \"import\"\n  \"in\"\n  \"instanceof\"\n  \"let\"\n  \"new\"\n  \"of\"\n  \"return\"\n  \"set\"\n  \"static\"\n  \"switch\"\n  \"target\"\n  \"throw\"\n  \"try\"\n  \"typeof\"\n  \"var\"\n  \"void\"\n  \"while\"\n  \"with\"\n  \"yield\"\n] @keyword\n"
  },
  {
    "path": "crates/ui/src/highlighter/languages/javascript/injections.scm",
    "content": "(((comment) @_jsdoc_comment\n  (#match? @_jsdoc_comment \"(?s)^/[*][*][^*].*[*]/$\")) @injection.content\n  (#set! injection.language \"jsdoc\"))\n\n((regex) @injection.content\n  (#set! injection.language \"regex\"))\n\n(call_expression\n  function: (identifier) @_name (#eq? @_name \"css\")\n  arguments: (template_string (string_fragment) @injection.content\n                              (#set! injection.language \"css\"))\n)\n\n(call_expression\n  function: (identifier) @_name (#eq? @_name \"html\")\n  arguments: (template_string) @injection.content\n                              (#set! injection.language \"html\")\n)\n\n(call_expression\n  function: (identifier) @_name (#eq? @_name \"js\")\n  arguments: (template_string (string_fragment) @injection.content\n                              (#set! injection.language \"javascript\"))\n)\n\n(call_expression\n  function: (identifier) @_name (#eq? @_name \"json\")\n  arguments: (template_string (string_fragment) @injection.content\n                              (#set! injection.language \"json\"))\n)\n\n(call_expression\n  function: (identifier) @_name (#eq? @_name \"sql\")\n  arguments: (template_string (string_fragment) @injection.content\n                              (#set! injection.language \"sql\"))\n)\n\n(call_expression\n  function: (identifier) @_name (#eq? @_name \"ts\")\n  arguments: (template_string (string_fragment) @injection.content\n                              (#set! injection.language \"typescript\"))\n)\n\n(call_expression\n  function: (identifier) @_name (#match? @_name \"^ya?ml$\")\n  arguments: (template_string (string_fragment) @injection.content\n                              (#set! injection.language \"yaml\"))\n)\n\n(call_expression\n  function: (identifier) @_name (#match? @_name \"^g(raph)?ql$\")\n  arguments: (template_string (string_fragment) @injection.content\n                              (#set! injection.language \"graphql\"))\n)\n"
  },
  {
    "path": "crates/ui/src/highlighter/languages/json/highlights.scm",
    "content": "(comment) @comment\n\n(string) @string\n(escape_sequence) @string.escape\n\n(number) @number\n\n(pair key: (string) @property)\n\n[\n  (true)\n  (false)\n] @boolean\n\n(null) @constant.builtin\n\n[\n  \",\"\n  \":\"\n  \"{\"\n  \"}\"\n  \"[\"\n  \"]\"\n] @punctuation\n"
  },
  {
    "path": "crates/ui/src/highlighter/languages/kotlin/highlights.scm",
    "content": ";; Kotlin highlights — adapted from tree-sitter-kotlin-sg (nvim-treesitter)\n;; Remapped to the highlight names used by this project's registry.\n;;\n;; IMPORTANT: Pattern order matters. This project's highlighter keeps the\n;; FIRST capture name when multiple patterns match the same range. Therefore\n;; specific patterns (function, property, constant…) must appear BEFORE the\n;; generic `(simple_identifier) @variable` catch-all at the end of this file.\n\n;;; Literals (no simple_identifier conflict — safe to place early)\n\n[\n\t(line_comment)\n\t(multiline_comment)\n\t(shebang_line)\n] @comment\n\n(real_literal) @number\n[\n\t(integer_literal)\n\t(long_literal)\n\t(hex_literal)\n\t(bin_literal)\n\t(unsigned_literal)\n] @number\n\n[\n\t(null_literal)\n\t(boolean_literal)\n] @boolean\n\n(character_literal) @string\n\n(string_literal) @string\n\n(character_escape_seq) @string.escape\n\n; Regex: \"pattern\".toRegex()\n(call_expression\n\t(navigation_expression\n\t\t((string_literal) @string.regex)\n\t\t(navigation_suffix\n\t\t\t((simple_identifier) @_function\n\t\t\t(#eq? @_function \"toRegex\")))))\n\n; Regex: Regex(\"pattern\")\n(call_expression\n\t((simple_identifier) @_function\n\t(#eq? @_function \"Regex\"))\n\t(call_suffix\n\t\t(value_arguments\n\t\t\t(value_argument\n\t\t\t\t(string_literal) @string.regex))))\n\n; Regex: Regex.fromLiteral(\"pattern\")\n(call_expression\n\t(navigation_expression\n\t\t((simple_identifier) @_class\n\t\t(#eq? @_class \"Regex\"))\n\t\t(navigation_suffix\n\t\t\t((simple_identifier) @_function\n\t\t\t(#eq? @_function \"fromLiteral\"))))\n\t(call_suffix\n\t\t(value_arguments\n\t\t\t(value_argument\n\t\t\t\t(string_literal) @string.regex))))\n\n;;; Keywords\n\n(type_alias \"typealias\" @keyword)\n[\n\t(class_modifier)\n\t(member_modifier)\n\t(function_modifier)\n\t(property_modifier)\n\t(platform_modifier)\n\t(variance_modifier)\n\t(parameter_modifier)\n\t(visibility_modifier)\n\t(reification_modifier)\n\t(inheritance_modifier)\n] @keyword\n\n[\n\t\"val\"\n\t\"var\"\n\t\"enum\"\n\t\"class\"\n\t\"object\"\n\t\"interface\"\n\t\"companion\"\n\t\"where\"\n\t\"by\"\n] @keyword\n\n(\"fun\") @keyword\n\n(jump_expression) @keyword\n\n[\n\t\"if\"\n\t\"else\"\n\t\"when\"\n] @keyword\n\n[\n\t\"for\"\n\t\"do\"\n\t\"while\"\n] @keyword\n\n[\n\t\"try\"\n\t\"catch\"\n\t\"throw\"\n\t\"finally\"\n] @keyword\n\n;;; Annotations\n\n(annotation\n\t\"@\" @attribute (use_site_target)? @attribute)\n(annotation\n\t(user_type\n\t\t(type_identifier) @attribute))\n(annotation\n\t(constructor_invocation\n\t\t(user_type\n\t\t\t(type_identifier) @attribute)))\n\n(file_annotation\n\t\"@\" @attribute \"file\" @attribute \":\" @attribute)\n(file_annotation\n\t(user_type\n\t\t(type_identifier) @attribute))\n(file_annotation\n\t(constructor_invocation\n\t\t(user_type\n\t\t\t(type_identifier) @attribute)))\n\n;;; Operators & Punctuation\n\n[\n\t\"!\"\n\t\"!=\"\n\t\"!==\"\n\t\"=\"\n\t\"==\"\n\t\"===\"\n\t\">\"\n\t\">=\"\n\t\"<\"\n\t\"<=\"\n\t\"||\"\n\t\"&&\"\n\t\"+\"\n\t\"++\"\n\t\"+=\"\n\t\"-\"\n\t\"--\"\n\t\"-=\"\n\t\"*\"\n\t\"*=\"\n\t\"/\"\n\t\"/=\"\n\t\"%\"\n\t\"%=\"\n\t\"?.\"\n\t\"?:\"\n\t\"!!\"\n\t\"is\"\n\t\"!is\"\n\t\"in\"\n\t\"!in\"\n\t\"as\"\n\t\"as?\"\n\t\"..\"\n\t\"->\"\n] @operator\n\n[\n\t\"(\" \")\"\n\t\"[\" \"]\"\n\t\"{\" \"}\"\n] @punctuation.bracket\n\n[\n\t\".\"\n\t\",\"\n\t\";\"\n\t\":\"\n\t\"::\"\n] @punctuation.delimiter\n\n(string_literal\n\t\"$\" @punctuation.special\n\t(interpolated_identifier) @variable)\n(string_literal\n\t\"${\" @punctuation.special\n\t(interpolated_expression)\n\t\"}\" @punctuation.special)\n\n;;; Types\n\n(type_identifier) @type\n\n;;; Package & Imports\n\n(package_header\n\t\"package\" @keyword\n\t. (identifier) @type)\n\n(import_header\n\t\"import\" @keyword)\n\n;;; Labels\n\n(label) @label\n\n;;; Function definitions\n\n(function_declaration\n\t. (simple_identifier) @function)\n\n(getter\n\t(\"get\") @function)\n(setter\n\t(\"set\") @function)\n\n(primary_constructor) @constructor\n(secondary_constructor\n\t(\"constructor\") @constructor)\n\n(constructor_invocation\n\t(user_type\n\t\t(type_identifier) @constructor))\n\n(anonymous_initializer\n\t(\"init\") @constructor)\n\n;;; Function calls — must appear before the generic @variable catch-all\n\n; function()\n(call_expression\n\t. (simple_identifier) @function)\n\n; object.function() or object.property.function()\n(call_expression\n\t(navigation_expression\n\t\t(navigation_suffix\n\t\t\t(simple_identifier) @function) . ))\n\n(call_expression\n\t. (simple_identifier) @function\n    (#any-of? @function\n\t\t\"arrayOf\"\n\t\t\"arrayOfNulls\"\n\t\t\"byteArrayOf\"\n\t\t\"shortArrayOf\"\n\t\t\"intArrayOf\"\n\t\t\"longArrayOf\"\n\t\t\"ubyteArrayOf\"\n\t\t\"ushortArrayOf\"\n\t\t\"uintArrayOf\"\n\t\t\"ulongArrayOf\"\n\t\t\"floatArrayOf\"\n\t\t\"doubleArrayOf\"\n\t\t\"booleanArrayOf\"\n\t\t\"charArrayOf\"\n\t\t\"emptyArray\"\n\t\t\"mapOf\"\n\t\t\"setOf\"\n\t\t\"listOf\"\n\t\t\"emptyMap\"\n\t\t\"emptySet\"\n\t\t\"emptyList\"\n\t\t\"mutableMapOf\"\n\t\t\"mutableSetOf\"\n\t\t\"mutableListOf\"\n\t\t\"print\"\n\t\t\"println\"\n\t\t\"error\"\n\t\t\"TODO\"\n\t\t\"run\"\n\t\t\"runCatching\"\n\t\t\"repeat\"\n\t\t\"lazy\"\n\t\t\"lazyOf\"\n\t\t\"enumValues\"\n\t\t\"enumValueOf\"\n\t\t\"assert\"\n\t\t\"check\"\n\t\t\"checkNotNull\"\n\t\t\"require\"\n\t\t\"requireNotNull\"\n\t\t\"with\"\n\t\t\"suspend\"\n\t\t\"synchronized\"\n))\n\n;;; Identifiers — specific patterns before the catch-all\n\n(enum_entry\n\t(simple_identifier) @constant)\n\n(class_parameter\n\t(simple_identifier) @property)\n\n(class_body\n\t(property_declaration\n\t\t(variable_declaration\n\t\t\t(simple_identifier) @property)))\n\n(_\n\t(navigation_suffix\n\t\t(simple_identifier) @property))\n\n(parameter\n\t(simple_identifier) @variable)\n\n(parameter_with_optional_type\n\t(simple_identifier) @variable)\n\n(lambda_literal\n\t(lambda_parameters\n\t\t(variable_declaration\n\t\t\t(simple_identifier) @variable)))\n\n; `this` / `super` keywords\n(this_expression) @variable.special\n(super_expression) @variable.special\n\n; `it` keyword inside lambdas\n((simple_identifier) @variable.special\n(#eq? @variable.special \"it\"))\n\n; `field` keyword inside property getter/setter\n((simple_identifier) @variable.special\n(#eq? @variable.special \"field\"))\n\n; `where` parsed as identifier when grammar doesn't recognise the clause\n((simple_identifier) @keyword\n(#eq? @keyword \"where\"))\n\n; Generic identifier catch-all — MUST be last so specific patterns win\n(simple_identifier) @variable\n"
  },
  {
    "path": "crates/ui/src/highlighter/languages/lua/highlights.scm",
    "content": ";; Lua highlights\n\n;;; Keywords\n\n[\n \"do\"\n \"else\"\n \"elseif\"\n \"end\"\n \"for\"\n \"function\"\n \"goto\"\n \"if\"\n \"in\"\n \"local\"\n \"repeat\"\n \"return\"\n \"then\"\n \"until\"\n \"while\"\n (break_statement)\n] @keyword\n\n;;; Operators\n\n[\n \"and\"\n \"not\"\n \"or\"\n] @keyword\n\n[\n \"+\"\n \"-\"\n \"*\"\n \"/\"\n \"%\"\n \"^\"\n \"#\"\n \"==\"\n \"~=\"\n \"<=\"\n \">=\"\n \"<\"\n \">\"\n \"=\"\n \"&\"\n \"~\"\n \"|\"\n \"<<\"\n \">>\"\n \"//\"\n \"..\"\n] @operator\n\n;;; Punctuations\n\n[\n \";\"\n \":\"\n \",\"\n \".\"\n] @punctuation.delimiter\n\n[\n \"(\"\n \")\"\n \"[\"\n \"]\"\n \"{\"\n \"}\"\n] @punctuation.bracket\n\n;;; Constants\n\n(nil) @boolean\n[\n (false)\n (true)\n] @boolean\n\n(vararg_expression) @constant\n\n((identifier) @constant\n (#match? @constant \"^[A-Z][A-Z_0-9]*$\"))\n\n;;; Numbers\n\n(number) @number\n\n;;; Strings\n\n(string) @string\n(escape_sequence) @string.escape\n\n;;; Comments\n\n(comment) @comment\n(hash_bang_line) @comment\n\n;;; Tables\n\n(field name: (identifier) @property)\n\n(dot_index_expression field: (identifier) @property)\n\n(table_constructor\n [\n \"{\"\n \"}\"\n ] @punctuation.bracket)\n\n;;; Functions\n\n(parameters (identifier) @variable)\n\n(function_call\n name: [\n (identifier) @function\n (dot_index_expression field: (identifier) @function)\n ])\n\n(function_declaration\n name: [\n (identifier) @function\n (dot_index_expression field: (identifier) @function)\n ])\n\n(method_index_expression method: (identifier) @function)\n\n;; Built-in functions\n(function_call\n (identifier) @function\n (#any-of? @function\n ;; built-in functions in Lua 5.1+\n \"assert\" \"collectgarbage\" \"dofile\" \"error\" \"getfenv\" \"getmetatable\" \"ipairs\"\n \"load\" \"loadfile\" \"loadstring\" \"module\" \"next\" \"pairs\" \"pcall\" \"print\"\n \"rawequal\" \"rawget\" \"rawset\" \"require\" \"select\" \"setfenv\" \"setmetatable\"\n \"tonumber\" \"tostring\" \"type\" \"unpack\" \"xpcall\"))\n\n;;; Variables\n\n(variable_list\n attribute: (attribute\n (identifier) @attribute))\n\n;; self reference\n((identifier) @variable.special\n (#eq? @variable.special \"self\"))\n\n;; Generic identifier\n(identifier) @variable\n"
  },
  {
    "path": "crates/ui/src/highlighter/languages/markdown/highlights.scm",
    "content": ";From nvim-treesitter/nvim-treesitter\n(atx_heading (inline) @title)\n(setext_heading (paragraph) @title)\n\n[\n  (atx_h1_marker)\n  (atx_h2_marker)\n  (atx_h3_marker)\n  (atx_h4_marker)\n  (atx_h5_marker)\n  (atx_h6_marker)\n  (setext_h1_underline)\n  (setext_h2_underline)\n] @punctuation.special\n\n[\n  (link_title)\n  (indented_code_block)\n  (fenced_code_block)\n] @text.literal\n\n[\n  (fenced_code_block_delimiter)\n] @punctuation.delimiter\n\n(code_fence_content) @none\n\n[\n  (link_destination)\n] @text.uri\n\n[\n  (link_label)\n] @text.reference\n\n[\n  (list_marker_plus)\n  (list_marker_minus)\n  (list_marker_star)\n  (list_marker_dot)\n  (list_marker_parenthesis)\n  (thematic_break)\n] @punctuation.list_marker\n\n[\n  (block_continuation)\n  (block_quote_marker)\n] @punctuation.special\n\n[\n  (backslash_escape)\n] @string.escape"
  },
  {
    "path": "crates/ui/src/highlighter/languages/markdown/injections.scm",
    "content": "(fenced_code_block\n  (info_string\n    (language) @injection.language)\n  (code_fence_content) @injection.content)\n\n((html_block) @injection.content (#set! injection.language \"html\"))\n\n(document . (section . (thematic_break) (_) @injection.content (thematic_break)) (#set! injection.language \"yaml\"))\n\n((minus_metadata) @injection.content (#set! injection.language \"yaml\"))\n\n((plus_metadata) @injection.content (#set! injection.language \"toml\"))\n\n((inline) @injection.content (#set! injection.language \"markdown_inline\"))"
  },
  {
    "path": "crates/ui/src/highlighter/languages/markdown_inline/highlights.scm",
    "content": "[\n  (emphasis_delimiter)\n  (code_span_delimiter)\n] @punctuation.delimiter\n\n(emphasis) @emphasis\n\n(strong_emphasis) @emphasis.strong\n\n[\n  (link_destination)\n  (uri_autolink)\n] @link_uri\n\n[\n  (link_label)\n  (link_text)\n  (image_description)\n] @link_text\n"
  },
  {
    "path": "crates/ui/src/highlighter/languages/php/injections.scm",
    "content": "; PHP injection rules\n; Based on tree-sitter-php injections.scm with added HTML support for text nodes\n\n((comment) @injection.content\n  (#set! injection.language \"phpdoc\"))\n\n(heredoc\n  (heredoc_body) @injection.content\n  (heredoc_end) @injection.language)\n\n(nowdoc\n  (nowdoc_body) @injection.content\n  (heredoc_end) @injection.language)\n\n; HTML in text nodes (content outside <?php ?> tags)\n; injection.combined tells the highlighter to merge all text nodes into a single\n; HTML document before parsing, so opening/closing tags across PHP blocks match.\n((text) @injection.content\n (#set! injection.language \"html\")\n (#set! injection.combined))\n"
  },
  {
    "path": "crates/ui/src/highlighter/languages/rust/README.md",
    "content": "https://github.com/tree-sitter/tree-sitter-rust/tree/master/queries"
  },
  {
    "path": "crates/ui/src/highlighter/languages/rust/highlights.scm",
    "content": "; Identifiers\n\n(type_identifier) @type\n(primitive_type) @type.builtin\n(field_identifier) @property\n\n; Identifier conventions\n\n; Assume all-caps names are constants\n((identifier) @constant\n (#match? @constant \"^[A-Z][A-Z\\\\d_]+$'\"))\n\n; Assume uppercase names are enum constructors\n((identifier) @constructor\n (#match? @constructor \"^[A-Z]\"))\n\n; Assume that uppercase names in paths are types\n((scoped_identifier\n  path: (identifier) @type)\n (#match? @type \"^[A-Z]\"))\n((scoped_identifier\n  path: (scoped_identifier\n    name: (identifier) @type))\n (#match? @type \"^[A-Z]\"))\n((scoped_type_identifier\n  path: (identifier) @type)\n (#match? @type \"^[A-Z]\"))\n((scoped_type_identifier\n  path: (scoped_identifier\n    name: (identifier) @type))\n (#match? @type \"^[A-Z]\"))\n\n; Assume all qualified names in struct patterns are enum constructors. (They're\n; either that, or struct names; highlighting both as constructors seems to be\n; the less glaring choice of error, visually.)\n(struct_pattern\n  type: (scoped_type_identifier\n    name: (type_identifier) @constructor))\n\n; Function calls\n\n(call_expression\n  function: (identifier) @function)\n(call_expression\n  function: (field_expression\n    field: (field_identifier) @function.method))\n(call_expression\n  function: (scoped_identifier\n    \"::\"\n    name: (identifier) @function))\n\n(generic_function\n  function: (identifier) @function)\n(generic_function\n  function: (scoped_identifier\n    name: (identifier) @function))\n(generic_function\n  function: (field_expression\n    field: (field_identifier) @function.method))\n\n(macro_invocation\n  macro: (identifier) @function.macro\n  \"!\" @function.macro)\n\n; Function definitions\n\n(function_item (identifier) @function)\n(function_signature_item (identifier) @function)\n\n[\n  (line_comment)\n  (block_comment)\n] @comment\n\n[\n  (line_comment (doc_comment))\n  (block_comment (doc_comment))\n] @comment.doc\n\n\"(\" @punctuation.bracket\n\")\" @punctuation.bracket\n\"[\" @punctuation.bracket\n\"]\" @punctuation.bracket\n\"{\" @punctuation.bracket\n\"}\" @punctuation.bracket\n\n(type_arguments\n  \"<\" @punctuation.bracket\n  \">\" @punctuation.bracket)\n(type_parameters\n  \"<\" @punctuation.bracket\n  \">\" @punctuation.bracket)\n\n\"::\" @punctuation.delimiter\n\":\" @punctuation.delimiter\n\".\" @punctuation.delimiter\n\",\" @punctuation.delimiter\n\";\" @punctuation.delimiter\n\n(parameter (identifier) @variable.parameter)\n\n(lifetime (identifier) @label)\n\n\"as\" @keyword\n\"async\" @keyword\n\"await\" @keyword\n\"break\" @keyword\n\"const\" @keyword\n\"continue\" @keyword\n\"default\" @keyword\n\"dyn\" @keyword\n\"else\" @keyword\n\"enum\" @keyword\n\"extern\" @keyword\n\"fn\" @keyword\n\"for\" @keyword\n\"gen\" @keyword\n\"if\" @keyword\n\"impl\" @keyword\n\"in\" @keyword\n\"let\" @keyword\n\"loop\" @keyword\n\"macro_rules!\" @keyword\n\"match\" @keyword\n\"mod\" @keyword\n\"move\" @keyword\n\"pub\" @keyword\n\"raw\" @keyword\n\"ref\" @keyword\n\"return\" @keyword\n\"static\" @keyword\n\"struct\" @keyword\n\"trait\" @keyword\n\"type\" @keyword\n\"union\" @keyword\n\"unsafe\" @keyword\n\"use\" @keyword\n\"where\" @keyword\n\"while\" @keyword\n\"yield\" @keyword\n(crate) @keyword\n(mutable_specifier) @keyword\n(use_list (self) @keyword)\n(scoped_use_list (self) @keyword)\n(scoped_identifier (self) @keyword)\n(super) @keyword\n\n(self) @variable.builtin\n\n(char_literal) @string\n(string_literal) @string\n(raw_string_literal) @string\n\n(boolean_literal) @constant.builtin\n(integer_literal) @constant.builtin\n(float_literal) @constant.builtin\n\n(escape_sequence) @escape\n\n(attribute_item) @attribute\n(inner_attribute_item) @attribute\n\n\"*\" @operator\n\"&\" @operator\n\"'\" @operator"
  },
  {
    "path": "crates/ui/src/highlighter/languages/rust/injections.scm",
    "content": "((macro_invocation\n  (token_tree) @injection.content)\n (#set! injection.language \"rust\")\n (#set! injection.include-children))\n\n((macro_rule\n  (token_tree) @injection.content)\n (#set! injection.language \"rust\")\n (#set! injection.include-children))"
  },
  {
    "path": "crates/ui/src/highlighter/languages/typescript/highlights.scm",
    "content": "; Types\n; Variables\n;----------\n\n(identifier) @variable\n\n; Properties\n;-----------\n\n(property_identifier) @property\n(shorthand_property_identifier) @property\n(shorthand_property_identifier_pattern) @property\n(private_property_identifier) @property\n\n; Function and method definitions\n;--------------------------------\n\n(function_expression\n  name: (identifier) @function)\n(function_declaration\n  name: (identifier) @function)\n(method_definition\n  name: (property_identifier) @function.method)\n\n(pair\n  key: (property_identifier) @function.method\n  value: [(function_expression) (arrow_function)])\n\n(assignment_expression\n  left: (member_expression\n    property: (property_identifier) @function.method)\n  right: [(function_expression) (arrow_function)])\n\n(variable_declarator\n  name: (identifier) @function\n  value: [(function_expression) (arrow_function)])\n\n(assignment_expression\n  left: (identifier) @function\n  right: [(function_expression) (arrow_function)])\n\n; Function and method calls\n;--------------------------\n\n(call_expression\n  function: (identifier) @function)\n\n(call_expression\n  function: (member_expression\n    property: (property_identifier) @function.method))\n\n; Special identifiers\n;--------------------\n\n((identifier) @type\n (#match? @type \"^[A-Z]\"))\n\n([\n  (identifier)\n  (shorthand_property_identifier)\n  (shorthand_property_identifier_pattern)\n ] @constant\n (#match? @constant \"^_*[A-Z_][A-Z\\\\d_]*$\"))\n\n((identifier) @variable.builtin\n (#match? @variable.builtin \"^(arguments|module|console|window|document)$\")\n (#is-not? local))\n\n((identifier) @function.builtin\n (#eq? @function.builtin \"require\")\n (#is-not? local))\n\n; Literals\n;---------\n\n(this) @variable.builtin\n(super) @variable.builtin\n\n[\n  (true)\n  (false)\n  (null)\n  (undefined)\n] @constant.builtin\n\n(comment) @comment\n\n[\n  (string)\n  (template_string)\n] @string\n\n(regex) @string.special\n(number) @number\n\n; Tokens\n;-------\n\n[\n  \";\"\n  (optional_chain)\n  \".\"\n  \",\"\n] @punctuation.delimiter\n\n[\n  \"-\"\n  \"--\"\n  \"-=\"\n  \"+\"\n  \"++\"\n  \"+=\"\n  \"*\"\n  \"*=\"\n  \"**\"\n  \"**=\"\n  \"/\"\n  \"/=\"\n  \"%\"\n  \"%=\"\n  \"<\"\n  \"<=\"\n  \"<<\"\n  \"<<=\"\n  \"=\"\n  \"==\"\n  \"===\"\n  \"!\"\n  \"!=\"\n  \"!==\"\n  \"=>\"\n  \">\"\n  \">=\"\n  \">>\"\n  \">>=\"\n  \">>>\"\n  \">>>=\"\n  \"~\"\n  \"^\"\n  \"&\"\n  \"|\"\n  \"^=\"\n  \"&=\"\n  \"|=\"\n  \"&&\"\n  \"||\"\n  \"??\"\n  \"&&=\"\n  \"||=\"\n  \"??=\"\n] @operator\n\n[\n  \"(\"\n  \")\"\n  \"[\"\n  \"]\"\n  \"{\"\n  \"}\"\n]  @punctuation.bracket\n\n(template_substitution\n  \"${\" @punctuation.special\n  \"}\" @punctuation.special) @embedded\n\n[\n  \"as\"\n  \"async\"\n  \"await\"\n  \"break\"\n  \"case\"\n  \"catch\"\n  \"class\"\n  \"const\"\n  \"continue\"\n  \"debugger\"\n  \"default\"\n  \"delete\"\n  \"do\"\n  \"else\"\n  \"export\"\n  \"extends\"\n  \"finally\"\n  \"for\"\n  \"from\"\n  \"function\"\n  \"get\"\n  \"if\"\n  \"import\"\n  \"in\"\n  \"instanceof\"\n  \"let\"\n  \"new\"\n  \"of\"\n  \"return\"\n  \"set\"\n  \"static\"\n  \"switch\"\n  \"target\"\n  \"throw\"\n  \"try\"\n  \"typeof\"\n  \"var\"\n  \"void\"\n  \"while\"\n  \"with\"\n  \"yield\"\n] @keyword\n\n\n(type_identifier) @type\n(predefined_type) @type.builtin\n\n((identifier) @type\n (#match? @type \"^[A-Z]\"))\n\n(type_arguments\n  \"<\" @punctuation.bracket\n  \">\" @punctuation.bracket)\n\n; Variables\n\n(required_parameter (identifier) @variable.parameter)\n(optional_parameter (identifier) @variable.parameter)\n\n; Keywords\n\n[ \"abstract\"\n  \"declare\"\n  \"enum\"\n  \"export\"\n  \"implements\"\n  \"interface\"\n  \"keyof\"\n  \"namespace\"\n  \"private\"\n  \"protected\"\n  \"public\"\n  \"type\"\n  \"readonly\"\n  \"override\"\n  \"satisfies\"\n] @keyword\n"
  },
  {
    "path": "crates/ui/src/highlighter/languages/zig/highlights.scm",
    "content": "; Variables\n\n(identifier) @variable\n\n; Parameters\n\n(parameter\n  name: (identifier) @variable.parameter)\n\n; Types\n\n(parameter\n  type: (identifier) @type)\n\n((identifier) @type\n  (#match? @type \"^[A-Z_][a-zA-Z0-9_]*\"))\n\n(variable_declaration\n  (identifier) @type\n  \"=\"\n  [\n    (struct_declaration)\n    (enum_declaration)\n    (union_declaration)\n    (opaque_declaration)\n  ])\n\n[\n  (builtin_type)\n  \"anyframe\"\n] @type.builtin\n\n; Constants\n\n((identifier) @constant\n  (#match? @constant \"^[A-Z][A-Z_0-9]+$\"))\n\n[\n  \"null\"\n  \"unreachable\"\n  \"undefined\"\n] @constant.builtin\n\n(field_expression\n  .\n  member: (identifier) @constant)\n\n(enum_declaration\n  (container_field\n    type: (identifier) @constant))\n\n; Labels\n\n(block_label (identifier) @label)\n\n(break_label (identifier) @label)\n\n; Fields\n\n(field_initializer\n  .\n  (identifier) @variable.member)\n\n(field_expression\n  (_)\n  member: (identifier) @variable.member)\n\n(container_field\n  name: (identifier) @variable.member)\n\n(initializer_list\n  (assignment_expression\n      left: (field_expression\n              .\n              member: (identifier) @variable.member)))\n\n; Functions\n\n(builtin_identifier) @function.builtin\n\n(call_expression\n  function: (identifier) @function.call)\n\n(call_expression\n  function: (field_expression\n    member: (identifier) @function.call))\n\n(function_declaration\n  name: (identifier) @function)\n\n; Modules\n\n(variable_declaration\n  (identifier) @module\n  (builtin_function\n    (builtin_identifier) @keyword.import\n    (#any-of? @keyword.import \"@import\" \"@cImport\")))\n\n; Builtins\n\n[\n  \"c\"\n  \"...\"\n] @variable.builtin\n\n((identifier) @variable.builtin\n  (#eq? @variable.builtin \"_\"))\n\n(calling_convention\n  (identifier) @variable.builtin)\n\n; Keywords\n\n[\n  \"asm\"\n  \"defer\"\n  \"errdefer\"\n  \"test\"\n  \"error\"\n  \"const\"\n  \"var\"\n] @keyword\n\n[\n  \"struct\"\n  \"union\"\n  \"enum\"\n  \"opaque\"\n] @keyword.type\n\n[\n  \"async\"\n  \"await\"\n  \"suspend\"\n  \"nosuspend\"\n  \"resume\"\n] @keyword.coroutine\n\n\"fn\" @keyword.function\n\n[\n  \"and\"\n  \"or\"\n  \"orelse\"\n] @keyword.operator\n\n\"return\" @keyword.return\n\n[\n  \"if\"\n  \"else\"\n  \"switch\"\n] @keyword.conditional\n\n[\n  \"for\"\n  \"while\"\n  \"break\"\n  \"continue\"\n] @keyword.repeat\n\n[\n  \"usingnamespace\"\n  \"export\"\n] @keyword.import\n\n[\n  \"try\"\n  \"catch\"\n] @keyword.exception\n\n[\n  \"volatile\"\n  \"allowzero\"\n  \"noalias\"\n  \"addrspace\"\n  \"align\"\n  \"callconv\"\n  \"linksection\"\n  \"pub\"\n  \"inline\"\n  \"noinline\"\n  \"extern\"\n  \"comptime\"\n  \"packed\"\n  \"threadlocal\"\n] @keyword.modifier\n\n; Operator\n\n[\n  \"=\"\n  \"*=\"\n  \"*%=\"\n  \"*|=\"\n  \"/=\"\n  \"%=\"\n  \"+=\"\n  \"+%=\"\n  \"+|=\"\n  \"-=\"\n  \"-%=\"\n  \"-|=\"\n  \"<<=\"\n  \"<<|=\"\n  \">>=\"\n  \"&=\"\n  \"^=\"\n  \"|=\"\n  \"!\"\n  \"~\"\n  \"-\"\n  \"-%\"\n  \"&\"\n  \"==\"\n  \"!=\"\n  \">\"\n  \">=\"\n  \"<=\"\n  \"<\"\n  \"&\"\n  \"^\"\n  \"|\"\n  \"<<\"\n  \">>\"\n  \"<<|\"\n  \"+\"\n  \"++\"\n  \"+%\"\n  \"-%\"\n  \"+|\"\n  \"-|\"\n  \"*\"\n  \"/\"\n  \"%\"\n  \"**\"\n  \"*%\"\n  \"*|\"\n  \"||\"\n  \".*\"\n  \".?\"\n  \"?\"\n  \"..\"\n] @operator\n\n; Literals\n\n(character) @character\n\n([\n  (string)\n  (multiline_string)\n] @string\n  (#set! \"priority\" 95))\n\n(integer) @number\n\n(float) @number.float\n\n(boolean) @boolean\n\n(escape_sequence) @string.escape\n\n; Punctuation\n\n[\n  \"[\"\n  \"]\"\n  \"(\"\n  \")\"\n  \"{\"\n  \"}\"\n] @punctuation.bracket\n\n[\n  \";\"\n  \".\"\n  \",\"\n  \":\"\n  \"=>\"\n  \"->\"\n] @punctuation.delimiter\n\n(payload \"|\" @punctuation.bracket)\n\n; Comments\n\n(comment) @comment @spell\n\n((comment) @comment.documentation\n  (#lua-match? @comment.documentation \"^//!\"))\n"
  },
  {
    "path": "crates/ui/src/highlighter/languages/zig/injections.scm",
    "content": "((comment) @injection.content\n  (#set! injection.language \"comment\"))\n"
  },
  {
    "path": "crates/ui/src/highlighter/languages.rs",
    "content": "use gpui::SharedString;\n\nuse crate::highlighter::LanguageConfig;\n\n#[cfg(not(feature = \"tree-sitter-languages\"))]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, enum_iterator::Sequence)]\npub enum Language {\n    Json,\n}\n\n#[cfg(feature = \"tree-sitter-languages\")]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, enum_iterator::Sequence)]\npub enum Language {\n    Json,\n    Plain,\n    Astro,\n    Bash,\n    C,\n    CMake,\n    CSharp,\n    Cpp,\n    Css,\n    Diff,\n    Ejs,\n    Elixir,\n    Erb,\n    Go,\n    GraphQL,\n    Html,\n    Java,\n    JavaScript,\n    JsDoc,\n    Kotlin,\n    Lua,\n    Make,\n    Markdown,\n    MarkdownInline,\n    Php,\n    Proto,\n    Python,\n    Ruby,\n    Rust,\n    Scala,\n    Sql,\n    Svelte,\n    Swift,\n    Toml,\n    Tsx,\n    TypeScript,\n    Yaml,\n    Zig,\n}\n\nimpl From<Language> for SharedString {\n    fn from(language: Language) -> Self {\n        language.name().into()\n    }\n}\n\nimpl Language {\n    pub fn all() -> impl Iterator<Item = Self> {\n        enum_iterator::all::<Language>()\n    }\n\n    pub fn name(&self) -> &'static str {\n        #[cfg(not(feature = \"tree-sitter-languages\"))]\n        return \"json\";\n\n        #[cfg(feature = \"tree-sitter-languages\")]\n        match self {\n            Self::Plain => \"text\",\n            Self::Astro => \"astro\",\n            Self::Bash => \"bash\",\n            Self::C => \"c\",\n            Self::CMake => \"cmake\",\n            Self::CSharp => \"csharp\",\n            Self::Cpp => \"cpp\",\n            Self::Css => \"css\",\n            Self::Diff => \"diff\",\n            Self::Ejs => \"ejs\",\n            Self::Elixir => \"elixir\",\n            Self::Erb => \"erb\",\n            Self::Go => \"go\",\n            Self::GraphQL => \"graphql\",\n            Self::Html => \"html\",\n            Self::Java => \"java\",\n            Self::JavaScript => \"javascript\",\n            Self::JsDoc => \"jsdoc\",\n            Self::Json => \"json\",\n            Self::Kotlin => \"kotlin\",\n            Self::Lua => \"lua\",\n            Self::Make => \"make\",\n            Self::Markdown => \"markdown\",\n            Self::MarkdownInline => \"markdown_inline\",\n            Self::Php => \"php\",\n            Self::Proto => \"proto\",\n            Self::Python => \"python\",\n            Self::Ruby => \"ruby\",\n            Self::Rust => \"rust\",\n            Self::Scala => \"scala\",\n            Self::Sql => \"sql\",\n            Self::Svelte => \"svelte\",\n            Self::Swift => \"swift\",\n            Self::Toml => \"toml\",\n            Self::Tsx => \"tsx\",\n            Self::TypeScript => \"typescript\",\n            Self::Yaml => \"yaml\",\n            Self::Zig => \"zig\",\n        }\n    }\n\n    #[allow(unused)]\n    pub fn from_str(s: &str) -> Self {\n        #[cfg(not(feature = \"tree-sitter-languages\"))]\n        return Self::Json;\n\n        #[cfg(feature = \"tree-sitter-languages\")]\n        match s {\n            \"astro\" => Self::Astro,\n            \"bash\" | \"sh\" => Self::Bash,\n            \"c\" => Self::C,\n            \"cmake\" => Self::CMake,\n            \"cpp\" | \"c++\" => Self::Cpp,\n            \"csharp\" | \"cs\" => Self::CSharp,\n            \"css\" | \"scss\" => Self::Css,\n            \"diff\" => Self::Diff,\n            \"ejs\" => Self::Ejs,\n            \"elixir\" | \"ex\" => Self::Elixir,\n            \"erb\" => Self::Erb,\n            \"go\" => Self::Go,\n            \"graphql\" => Self::GraphQL,\n            \"html\" => Self::Html,\n            \"java\" => Self::Java,\n            \"javascript\" | \"js\" => Self::JavaScript,\n            \"jsdoc\" => Self::JsDoc,\n            \"json\" | \"jsonc\" => Self::Json,\n            \"kt\" | \"kts\" | \"ktm\" => Self::Kotlin,\n            \"lua\" => Self::Lua,\n            \"make\" | \"makefile\" => Self::Make,\n            \"markdown\" | \"md\" | \"mdx\" => Self::Markdown,\n            \"markdown_inline\" | \"markdown-inline\" => Self::MarkdownInline,\n            \"php\" | \"php3\" | \"php4\" | \"php5\" | \"phtml\" => Self::Php,\n            \"proto\" | \"protobuf\" => Self::Proto,\n            \"python\" | \"py\" => Self::Python,\n            \"ruby\" | \"rb\" => Self::Ruby,\n            \"rust\" | \"rs\" => Self::Rust,\n            \"scala\" => Self::Scala,\n            \"sql\" => Self::Sql,\n            \"svelte\" => Self::Svelte,\n            \"swift\" => Self::Swift,\n            \"toml\" => Self::Toml,\n            \"tsx\" => Self::Tsx,\n            \"typescript\" | \"ts\" => Self::TypeScript,\n            \"yaml\" | \"yml\" => Self::Yaml,\n            \"zig\" => Self::Zig,\n            _ => Self::Plain,\n        }\n    }\n\n    #[allow(unused)]\n    pub(super) fn injection_languages(&self) -> Vec<SharedString> {\n        #[cfg(not(feature = \"tree-sitter-languages\"))]\n        return vec![];\n\n        #[cfg(feature = \"tree-sitter-languages\")]\n        match self {\n            Self::Markdown => vec![\"markdown-inline\", \"html\", \"toml\", \"yaml\"],\n            Self::MarkdownInline => vec![],\n            Self::Html => vec![\"javascript\", \"css\"],\n            Self::Rust => vec![\"rust\"],\n            Self::JavaScript | Self::TypeScript => vec![\n                \"jsdoc\",\n                \"json\",\n                \"css\",\n                \"html\",\n                \"sql\",\n                \"typescript\",\n                \"javascript\",\n                \"tsx\",\n                \"yaml\",\n                \"graphql\",\n            ],\n            Self::Astro => vec![\"html\", \"css\", \"javascript\", \"typescript\"],\n            Self::Php => vec![\n                \"php\",\n                \"html\",\n                \"css\",\n                \"javascript\",\n                \"json\",\n                \"jsdoc\",\n                \"graphql\",\n            ],\n            Self::Svelte => vec![\"svelte\", \"html\", \"css\", \"typescript\"],\n            _ => vec![],\n        }\n        .into_iter()\n        .map(|s| s.into())\n        .collect()\n    }\n\n    /// Return the language info for the language.\n    ///\n    /// (language, query, injection, locals)\n    pub(super) fn config(&self) -> LanguageConfig {\n        #[cfg(not(feature = \"tree-sitter-languages\"))]\n        let (language, query, injection, locals) = match self {\n            Self::Json => (\n                tree_sitter_json::LANGUAGE,\n                include_str!(\"languages/json/highlights.scm\"),\n                \"\",\n                \"\",\n            ),\n        };\n\n        #[cfg(feature = \"tree-sitter-languages\")]\n        let (language, query, injection, locals) = match self {\n            Self::Plain => (tree_sitter_json::LANGUAGE, \"\", \"\", \"\"),\n            Self::Json => (\n                tree_sitter_json::LANGUAGE,\n                include_str!(\"languages/json/highlights.scm\"),\n                \"\",\n                \"\",\n            ),\n            Self::Markdown => (\n                tree_sitter_md::LANGUAGE,\n                include_str!(\"languages/markdown/highlights.scm\"),\n                include_str!(\"languages/markdown/injections.scm\"),\n                \"\",\n            ),\n            Self::MarkdownInline => (\n                tree_sitter_md::INLINE_LANGUAGE,\n                include_str!(\"languages/markdown_inline/highlights.scm\"),\n                \"\",\n                \"\",\n            ),\n            Self::Toml => (\n                tree_sitter_toml_ng::LANGUAGE,\n                tree_sitter_toml_ng::HIGHLIGHTS_QUERY,\n                \"\",\n                \"\",\n            ),\n            Self::Yaml => (\n                tree_sitter_yaml::LANGUAGE,\n                tree_sitter_yaml::HIGHLIGHTS_QUERY,\n                \"\",\n                \"\",\n            ),\n            Self::Rust => (\n                tree_sitter_rust::LANGUAGE,\n                include_str!(\"languages/rust/highlights.scm\"),\n                include_str!(\"languages/rust/injections.scm\"),\n                \"\",\n            ),\n            Self::Go => (\n                tree_sitter_go::LANGUAGE,\n                include_str!(\"languages/go/highlights.scm\"),\n                \"\",\n                \"\",\n            ),\n            Self::C => (\n                tree_sitter_c::LANGUAGE,\n                tree_sitter_c::HIGHLIGHT_QUERY,\n                \"\",\n                \"\",\n            ),\n            Self::Cpp => (\n                tree_sitter_cpp::LANGUAGE,\n                tree_sitter_cpp::HIGHLIGHT_QUERY,\n                \"\",\n                \"\",\n            ),\n            Self::JavaScript => (\n                tree_sitter_javascript::LANGUAGE,\n                include_str!(\"languages/javascript/highlights.scm\"),\n                include_str!(\"languages/javascript/injections.scm\"),\n                tree_sitter_javascript::LOCALS_QUERY,\n            ),\n            Self::JsDoc => (\n                tree_sitter_jsdoc::LANGUAGE,\n                tree_sitter_jsdoc::HIGHLIGHTS_QUERY,\n                \"\",\n                \"\",\n            ),\n            Self::Zig => (\n                tree_sitter_zig::LANGUAGE,\n                include_str!(\"languages/zig/highlights.scm\"),\n                include_str!(\"languages/zig/injections.scm\"),\n                \"\",\n            ),\n            Self::Java => (\n                tree_sitter_java::LANGUAGE,\n                tree_sitter_java::HIGHLIGHTS_QUERY,\n                \"\",\n                \"\",\n            ),\n            Self::Python => (\n                tree_sitter_python::LANGUAGE,\n                tree_sitter_python::HIGHLIGHTS_QUERY,\n                \"\",\n                \"\",\n            ),\n            Self::Ruby => (\n                tree_sitter_ruby::LANGUAGE,\n                tree_sitter_ruby::HIGHLIGHTS_QUERY,\n                \"\",\n                tree_sitter_ruby::LOCALS_QUERY,\n            ),\n            Self::Bash => (\n                tree_sitter_bash::LANGUAGE,\n                tree_sitter_bash::HIGHLIGHT_QUERY,\n                \"\",\n                \"\",\n            ),\n            Self::Html => (\n                tree_sitter_html::LANGUAGE,\n                include_str!(\"languages/html/highlights.scm\"),\n                include_str!(\"languages/html/injections.scm\"),\n                \"\",\n            ),\n            Self::Css => (\n                tree_sitter_css::LANGUAGE,\n                tree_sitter_css::HIGHLIGHTS_QUERY,\n                \"\",\n                \"\",\n            ),\n            Self::Swift => (tree_sitter_swift::LANGUAGE, \"\", \"\", \"\"),\n            Self::Scala => (\n                tree_sitter_scala::LANGUAGE,\n                tree_sitter_scala::HIGHLIGHTS_QUERY,\n                \"\",\n                tree_sitter_scala::LOCALS_QUERY,\n            ),\n            Self::Sql => (\n                tree_sitter_sequel::LANGUAGE,\n                tree_sitter_sequel::HIGHLIGHTS_QUERY,\n                \"\",\n                \"\",\n            ),\n            Self::CSharp => (tree_sitter_c_sharp::LANGUAGE, \"\", \"\", \"\"),\n            Self::GraphQL => (tree_sitter_graphql::LANGUAGE, \"\", \"\", \"\"),\n            Self::Proto => (tree_sitter_proto::LANGUAGE, \"\", \"\", \"\"),\n            Self::Make => (\n                tree_sitter_make::LANGUAGE,\n                tree_sitter_make::HIGHLIGHTS_QUERY,\n                \"\",\n                \"\",\n            ),\n            Self::CMake => (tree_sitter_cmake::LANGUAGE, \"\", \"\", \"\"),\n            Self::TypeScript => (\n                tree_sitter_typescript::LANGUAGE_TYPESCRIPT,\n                include_str!(\"languages/typescript/highlights.scm\"),\n                include_str!(\"languages/javascript/injections.scm\"),\n                tree_sitter_typescript::LOCALS_QUERY,\n            ),\n            Self::Tsx => (\n                tree_sitter_typescript::LANGUAGE_TSX,\n                tree_sitter_typescript::HIGHLIGHTS_QUERY,\n                \"\",\n                tree_sitter_typescript::LOCALS_QUERY,\n            ),\n            Self::Diff => (\n                tree_sitter_diff::LANGUAGE,\n                tree_sitter_diff::HIGHLIGHTS_QUERY,\n                \"\",\n                \"\",\n            ),\n            Self::Elixir => (\n                tree_sitter_elixir::LANGUAGE,\n                tree_sitter_elixir::HIGHLIGHTS_QUERY,\n                tree_sitter_elixir::INJECTIONS_QUERY,\n                \"\",\n            ),\n            Self::Erb => (\n                tree_sitter_embedded_template::LANGUAGE,\n                tree_sitter_embedded_template::HIGHLIGHTS_QUERY,\n                tree_sitter_embedded_template::INJECTIONS_EJS_QUERY,\n                \"\",\n            ),\n            Self::Ejs => (\n                tree_sitter_embedded_template::LANGUAGE,\n                tree_sitter_embedded_template::HIGHLIGHTS_QUERY,\n                tree_sitter_embedded_template::INJECTIONS_EJS_QUERY,\n                \"\",\n            ),\n            Self::Php => (\n                tree_sitter_php::LANGUAGE_PHP,\n                tree_sitter_php::HIGHLIGHTS_QUERY,\n                include_str!(\"languages/php/injections.scm\"),\n                \"\",\n            ),\n            Self::Astro => (\n                tree_sitter_astro_next::LANGUAGE,\n                tree_sitter_astro_next::HIGHLIGHTS_QUERY,\n                tree_sitter_astro_next::INJECTIONS_QUERY,\n                \"\",\n            ),\n            Self::Kotlin => (\n                tree_sitter_kotlin_sg::LANGUAGE,\n                include_str!(\"languages/kotlin/highlights.scm\"),\n                \"\",\n                \"\",\n            ),\n            Self::Lua => (\n                tree_sitter_lua::LANGUAGE,\n                include_str!(\"languages/lua/highlights.scm\"),\n                tree_sitter_lua::INJECTIONS_QUERY,\n                tree_sitter_lua::LOCALS_QUERY,\n            ),\n            Self::Svelte => (\n                tree_sitter_svelte_next::LANGUAGE,\n                tree_sitter_svelte_next::HIGHLIGHTS_QUERY,\n                tree_sitter_svelte_next::INJECTIONS_QUERY,\n                tree_sitter_svelte_next::LOCALS_QUERY,\n            ),\n        };\n\n        let language = tree_sitter::Language::new(language);\n\n        LanguageConfig::new(\n            self.name(),\n            language,\n            self.injection_languages(),\n            query,\n            injection,\n            locals,\n        )\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    #[test]\n    #[cfg(feature = \"tree-sitter-languages\")]\n    fn test_language_name() {\n        use super::*;\n\n        assert_eq!(Language::MarkdownInline.name(), \"markdown_inline\");\n        assert_eq!(Language::Markdown.name(), \"markdown\");\n        assert_eq!(Language::Json.name(), \"json\");\n        assert_eq!(Language::Yaml.name(), \"yaml\");\n        assert_eq!(Language::Rust.name(), \"rust\");\n        assert_eq!(Language::Go.name(), \"go\");\n        assert_eq!(Language::C.name(), \"c\");\n        assert_eq!(Language::Cpp.name(), \"cpp\");\n        assert_eq!(Language::Sql.name(), \"sql\");\n        assert_eq!(Language::JavaScript.name(), \"javascript\");\n        assert_eq!(Language::Zig.name(), \"zig\");\n        assert_eq!(Language::CSharp.name(), \"csharp\");\n        assert_eq!(Language::TypeScript.name(), \"typescript\");\n        assert_eq!(Language::Tsx.name(), \"tsx\");\n        assert_eq!(Language::Diff.name(), \"diff\");\n        assert_eq!(Language::Elixir.name(), \"elixir\");\n        assert_eq!(Language::Erb.name(), \"erb\");\n        assert_eq!(Language::Ejs.name(), \"ejs\");\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/highlighter/mod.rs",
    "content": "// Diagnostics module - works on all platforms (no tree-sitter dependency)\nmod diagnostics;\npub use diagnostics::*;\n\n// Native implementation with full tree-sitter support\n#[cfg(not(target_family = \"wasm\"))]\nmod highlighter;\n#[cfg(not(target_family = \"wasm\"))]\nmod languages;\n#[cfg(not(target_family = \"wasm\"))]\nmod registry;\n\n#[cfg(not(target_family = \"wasm\"))]\npub use highlighter::*;\n#[cfg(not(target_family = \"wasm\"))]\npub use languages::*;\n#[cfg(not(target_family = \"wasm\"))]\npub use registry::*;\n\n// WASM stub implementation (no tree-sitter support)\n#[cfg(target_family = \"wasm\")]\nmod wasm_stub;\n#[cfg(target_family = \"wasm\")]\npub use wasm_stub::*;\n"
  },
  {
    "path": "crates/ui/src/highlighter/registry.rs",
    "content": "use gpui::{App, FontWeight, HighlightStyle, Hsla, SharedString};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse serde_repr::{Deserialize_repr, Serialize_repr};\nuse std::{\n    collections::HashMap,\n    ops::Deref,\n    sync::{Arc, LazyLock, Mutex},\n};\n\nuse crate::{\n    ActiveTheme, DEFAULT_THEME_COLORS, ThemeMode,\n    highlighter::{Language, languages},\n};\n\npub(super) const HIGHLIGHT_NAMES: [&str; 40] = [\n    \"attribute\",\n    \"boolean\",\n    \"comment\",\n    \"comment.doc\",\n    \"constant\",\n    \"constructor\",\n    \"embedded\",\n    \"emphasis\",\n    \"emphasis.strong\",\n    \"enum\",\n    \"function\",\n    \"hint\",\n    \"keyword\",\n    \"label\",\n    \"link_text\",\n    \"link_uri\",\n    \"number\",\n    \"operator\",\n    \"predictive\",\n    \"preproc\",\n    \"primary\",\n    \"property\",\n    \"punctuation\",\n    \"punctuation.bracket\",\n    \"punctuation.delimiter\",\n    \"punctuation.list_marker\",\n    \"punctuation.special\",\n    \"string\",\n    \"string.escape\",\n    \"string.regex\",\n    \"string.special\",\n    \"string.special.symbol\",\n    \"tag\",\n    \"tag.doctype\",\n    \"text.literal\",\n    \"title\",\n    \"type\",\n    \"variable\",\n    \"variable.special\",\n    \"variant\",\n];\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct LanguageConfig {\n    pub name: SharedString,\n    pub language: tree_sitter::Language,\n    pub injection_languages: Vec<SharedString>,\n    pub highlights: SharedString,\n    pub injections: SharedString,\n    pub locals: SharedString,\n}\n\nimpl LanguageConfig {\n    pub fn new(\n        name: impl Into<SharedString>,\n        language: tree_sitter::Language,\n        injection_languages: Vec<SharedString>,\n        highlights: &str,\n        injections: &str,\n        locals: &str,\n    ) -> Self {\n        Self {\n            name: name.into(),\n            language,\n            injection_languages,\n            highlights: SharedString::from(highlights.to_string()),\n            injections: SharedString::from(injections.to_string()),\n            locals: SharedString::from(locals.to_string()),\n        }\n    }\n}\n\n/// Theme for Tree-sitter Highlight\n///\n/// https://docs.rs/tree-sitter-highlight/0.25.4/tree_sitter_highlight/\n#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]\npub struct SyntaxColors {\n    pub attribute: Option<ThemeStyle>,\n    pub boolean: Option<ThemeStyle>,\n    pub comment: Option<ThemeStyle>,\n    pub comment_doc: Option<ThemeStyle>,\n    pub constant: Option<ThemeStyle>,\n    pub constructor: Option<ThemeStyle>,\n    pub embedded: Option<ThemeStyle>,\n    pub emphasis: Option<ThemeStyle>,\n    #[serde(rename = \"emphasis.strong\")]\n    pub emphasis_strong: Option<ThemeStyle>,\n    #[serde(rename = \"enum\")]\n    pub enum_: Option<ThemeStyle>,\n    pub function: Option<ThemeStyle>,\n    pub hint: Option<ThemeStyle>,\n    pub keyword: Option<ThemeStyle>,\n    pub label: Option<ThemeStyle>,\n    #[serde(rename = \"link_text\")]\n    pub link_text: Option<ThemeStyle>,\n    #[serde(rename = \"link_uri\")]\n    pub link_uri: Option<ThemeStyle>,\n    pub number: Option<ThemeStyle>,\n    pub operator: Option<ThemeStyle>,\n    pub predictive: Option<ThemeStyle>,\n    pub preproc: Option<ThemeStyle>,\n    pub primary: Option<ThemeStyle>,\n    pub property: Option<ThemeStyle>,\n    pub punctuation: Option<ThemeStyle>,\n    #[serde(rename = \"punctuation.bracket\")]\n    pub punctuation_bracket: Option<ThemeStyle>,\n    #[serde(rename = \"punctuation.delimiter\")]\n    pub punctuation_delimiter: Option<ThemeStyle>,\n    #[serde(rename = \"punctuation.list_marker\")]\n    pub punctuation_list_marker: Option<ThemeStyle>,\n    #[serde(rename = \"punctuation.special\")]\n    pub punctuation_special: Option<ThemeStyle>,\n    pub string: Option<ThemeStyle>,\n    #[serde(rename = \"string.escape\")]\n    pub string_escape: Option<ThemeStyle>,\n    #[serde(rename = \"string.regex\")]\n    pub string_regex: Option<ThemeStyle>,\n    #[serde(rename = \"string.special\")]\n    pub string_special: Option<ThemeStyle>,\n    #[serde(rename = \"string.special.symbol\")]\n    pub string_special_symbol: Option<ThemeStyle>,\n    pub tag: Option<ThemeStyle>,\n    #[serde(rename = \"tag.doctype\")]\n    pub tag_doctype: Option<ThemeStyle>,\n    #[serde(rename = \"text.literal\")]\n    pub text_literal: Option<ThemeStyle>,\n    pub title: Option<ThemeStyle>,\n    #[serde(rename = \"type\")]\n    pub type_: Option<ThemeStyle>,\n    pub variable: Option<ThemeStyle>,\n    #[serde(rename = \"variable.special\")]\n    pub variable_special: Option<ThemeStyle>,\n    pub variant: Option<ThemeStyle>,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum FontStyle {\n    Normal,\n    Italic,\n    Underline,\n}\n\nimpl From<FontStyle> for gpui::FontStyle {\n    fn from(style: FontStyle) -> Self {\n        match style {\n            FontStyle::Normal => gpui::FontStyle::Normal,\n            FontStyle::Italic => gpui::FontStyle::Italic,\n            FontStyle::Underline => gpui::FontStyle::Normal,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize_repr, Deserialize_repr, JsonSchema)]\n#[repr(u16)]\npub enum FontWeightContent {\n    Thin = 100,\n    ExtraLight = 200,\n    Light = 300,\n    Normal = 400,\n    Medium = 500,\n    Semibold = 600,\n    Bold = 700,\n    ExtraBold = 800,\n    Black = 900,\n}\n\nimpl From<FontWeightContent> for FontWeight {\n    fn from(value: FontWeightContent) -> Self {\n        match value {\n            FontWeightContent::Thin => FontWeight::THIN,\n            FontWeightContent::ExtraLight => FontWeight::EXTRA_LIGHT,\n            FontWeightContent::Light => FontWeight::LIGHT,\n            FontWeightContent::Normal => FontWeight::NORMAL,\n            FontWeightContent::Medium => FontWeight::MEDIUM,\n            FontWeightContent::Semibold => FontWeight::SEMIBOLD,\n            FontWeightContent::Bold => FontWeight::BOLD,\n            FontWeightContent::ExtraBold => FontWeight::EXTRA_BOLD,\n            FontWeightContent::Black => FontWeight::BLACK,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]\npub struct ThemeStyle {\n    color: Option<Hsla>,\n    font_style: Option<FontStyle>,\n    font_weight: Option<FontWeightContent>,\n}\n\nimpl From<ThemeStyle> for HighlightStyle {\n    fn from(style: ThemeStyle) -> Self {\n        HighlightStyle {\n            color: style.color,\n            font_weight: style.font_weight.map(Into::into),\n            font_style: style.font_style.map(Into::into),\n            ..Default::default()\n        }\n    }\n}\n\nimpl SyntaxColors {\n    pub fn style(&self, name: &str) -> Option<HighlightStyle> {\n        if name.is_empty() {\n            return None;\n        }\n\n        let style = match name {\n            \"attribute\" => self.attribute,\n            \"boolean\" => self.boolean,\n            \"comment\" => self.comment,\n            \"comment.doc\" => self.comment_doc,\n            \"constant\" => self.constant,\n            \"constructor\" => self.constructor,\n            \"embedded\" => self.embedded,\n            \"emphasis\" => self.emphasis,\n            \"emphasis.strong\" => self.emphasis_strong,\n            \"enum\" => self.enum_,\n            \"function\" => self.function,\n            \"hint\" => self.hint,\n            \"keyword\" => self.keyword,\n            \"label\" => self.label,\n            \"link_text\" => self.link_text,\n            \"link_uri\" => self.link_uri,\n            \"number\" => self.number,\n            \"operator\" => self.operator,\n            \"predictive\" => self.predictive,\n            \"preproc\" => self.preproc,\n            \"primary\" => self.primary,\n            \"property\" => self.property,\n            \"punctuation\" => self.punctuation,\n            \"punctuation.bracket\" => self.punctuation_bracket,\n            \"punctuation.delimiter\" => self.punctuation_delimiter,\n            \"punctuation.list_marker\" => self.punctuation_list_marker,\n            \"punctuation.special\" => self.punctuation_special,\n            \"string\" => self.string,\n            \"string.escape\" => self.string_escape,\n            \"string.regex\" => self.string_regex,\n            \"string.special\" => self.string_special,\n            \"string.special.symbol\" => self.string_special_symbol,\n            \"tag\" => self.tag,\n            \"tag.doctype\" => self.tag_doctype,\n            \"text.literal\" => self.text_literal,\n            \"title\" => self.title,\n            \"type\" => self.type_,\n            \"variable\" => self.variable,\n            \"variable.special\" => self.variable_special,\n            \"variant\" => self.variant,\n            _ => None,\n        }\n        .map(|s| s.into());\n\n        if style.is_some() {\n            style\n        } else {\n            // Fallback `keyword.modifier` to `keyword`\n            if name.contains(\".\") {\n                if let Some(prefix) = name.split(\".\").next() {\n                    return self.style(prefix);\n                }\n\n                None\n            } else {\n                None\n            }\n        }\n    }\n\n    #[inline]\n    pub fn style_for_index(&self, index: usize) -> Option<HighlightStyle> {\n        HIGHLIGHT_NAMES.get(index).and_then(|name| self.style(name))\n    }\n}\n\n#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]\npub struct StatusColors {\n    #[serde(rename = \"error\")]\n    error: Option<Hsla>,\n    #[serde(rename = \"error.background\")]\n    error_background: Option<Hsla>,\n    #[serde(rename = \"error.border\")]\n    error_border: Option<Hsla>,\n    #[serde(rename = \"warning\")]\n    warning: Option<Hsla>,\n    #[serde(rename = \"warning.background\")]\n    warning_background: Option<Hsla>,\n    #[serde(rename = \"warning.border\")]\n    warning_border: Option<Hsla>,\n    #[serde(rename = \"info\")]\n    info: Option<Hsla>,\n    #[serde(rename = \"info.background\")]\n    info_background: Option<Hsla>,\n    #[serde(rename = \"info.border\")]\n    info_border: Option<Hsla>,\n    #[serde(rename = \"success\")]\n    success: Option<Hsla>,\n    #[serde(rename = \"success.background\")]\n    success_background: Option<Hsla>,\n    #[serde(rename = \"success.border\")]\n    success_border: Option<Hsla>,\n    #[serde(rename = \"hint\")]\n    hint: Option<Hsla>,\n    #[serde(rename = \"hint.background\")]\n    hint_background: Option<Hsla>,\n    #[serde(rename = \"hint.border\")]\n    hint_border: Option<Hsla>,\n}\n\nimpl StatusColors {\n    #[inline]\n    pub fn error(&self, cx: &App) -> Hsla {\n        self.error.unwrap_or(cx.theme().red)\n    }\n\n    #[inline]\n    pub fn error_background(&self, cx: &App) -> Hsla {\n        let bg = cx.theme().background;\n        self.error_background\n            .unwrap_or(bg.blend(self.error(cx).alpha(0.2)))\n    }\n\n    #[inline]\n    pub fn error_border(&self, cx: &App) -> Hsla {\n        self.error_border.unwrap_or(self.error(cx))\n    }\n\n    #[inline]\n    pub fn warning(&self, cx: &App) -> Hsla {\n        self.warning.unwrap_or(cx.theme().yellow)\n    }\n\n    #[inline]\n    pub fn warning_background(&self, cx: &App) -> Hsla {\n        let bg = cx.theme().background;\n        self.warning_background\n            .unwrap_or(bg.blend(self.warning(cx).alpha(0.2)))\n    }\n\n    #[inline]\n    pub fn warning_border(&self, cx: &App) -> Hsla {\n        self.warning_border.unwrap_or(self.warning(cx))\n    }\n\n    #[inline]\n    pub fn info(&self, cx: &App) -> Hsla {\n        self.info.unwrap_or(cx.theme().blue)\n    }\n\n    #[inline]\n    pub fn info_background(&self, cx: &App) -> Hsla {\n        let bg = cx.theme().background;\n        self.info_background\n            .unwrap_or(bg.blend(self.info(cx).alpha(0.2)))\n    }\n\n    #[inline]\n    pub fn info_border(&self, cx: &App) -> Hsla {\n        self.info_border.unwrap_or(self.info(cx))\n    }\n\n    #[inline]\n    pub fn success(&self, cx: &App) -> Hsla {\n        self.success.unwrap_or(cx.theme().green)\n    }\n\n    #[inline]\n    pub fn success_background(&self, cx: &App) -> Hsla {\n        let bg = cx.theme().background;\n        self.success_background\n            .unwrap_or(bg.blend(self.success(cx).alpha(0.2)))\n    }\n\n    #[inline]\n    pub fn success_border(&self, cx: &App) -> Hsla {\n        self.success_border.unwrap_or(self.success(cx))\n    }\n\n    #[inline]\n    pub fn hint(&self, cx: &App) -> Hsla {\n        self.hint.unwrap_or(cx.theme().cyan)\n    }\n\n    #[inline]\n    pub fn hint_background(&self, cx: &App) -> Hsla {\n        let bg = cx.theme().background;\n        self.hint_background\n            .unwrap_or(bg.blend(self.hint(cx).alpha(0.2)))\n    }\n\n    #[inline]\n    pub fn hint_border(&self, cx: &App) -> Hsla {\n        self.hint_border.unwrap_or(self.hint(cx))\n    }\n}\n\n#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]\npub struct HighlightThemeStyle {\n    #[serde(rename = \"editor.background\")]\n    pub editor_background: Option<Hsla>,\n    #[serde(rename = \"editor.foreground\")]\n    pub editor_foreground: Option<Hsla>,\n    #[serde(rename = \"editor.active_line.background\")]\n    pub editor_active_line: Option<Hsla>,\n    #[serde(rename = \"editor.line_number\")]\n    pub editor_line_number: Option<Hsla>,\n    #[serde(rename = \"editor.active_line_number\")]\n    pub editor_active_line_number: Option<Hsla>,\n    #[serde(rename = \"editor.invisible\")]\n    pub editor_invisible: Option<Hsla>,\n    #[serde(flatten)]\n    pub status: StatusColors,\n    #[serde(rename = \"syntax\")]\n    pub syntax: SyntaxColors,\n}\n\n/// Theme for Tree-sitter Highlight from JSON theme file.\n///\n/// This json is compatible with the Zed theme format.\n///\n/// https://zed.dev/docs/extensions/languages#syntax-highlighting\n#[derive(Debug, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]\npub struct HighlightTheme {\n    pub name: String,\n    #[serde(default)]\n    pub appearance: ThemeMode,\n    pub style: HighlightThemeStyle,\n}\n\nimpl Deref for HighlightTheme {\n    type Target = SyntaxColors;\n\n    fn deref(&self) -> &Self::Target {\n        &self.style.syntax\n    }\n}\n\nimpl HighlightTheme {\n    pub fn default_dark() -> Arc<Self> {\n        DEFAULT_THEME_COLORS[&ThemeMode::Dark].1.clone()\n    }\n\n    pub fn default_light() -> Arc<Self> {\n        DEFAULT_THEME_COLORS[&ThemeMode::Light].1.clone()\n    }\n}\n\n/// Registry for code highlighter languages.\npub struct LanguageRegistry {\n    languages: Mutex<HashMap<SharedString, LanguageConfig>>,\n}\n\nimpl LanguageRegistry {\n    /// Returns the singleton instance of the `LanguageRegistry` with default languages and themes.\n    pub fn singleton() -> &'static LazyLock<LanguageRegistry> {\n        static INSTANCE: LazyLock<LanguageRegistry> = LazyLock::new(|| LanguageRegistry {\n            languages: Mutex::new(\n                languages::Language::all()\n                    .map(|language| (language.name().into(), language.config()))\n                    .collect(),\n            ),\n        });\n        &INSTANCE\n    }\n\n    /// Registers a new language configuration to the registry.\n    pub fn register(&self, lang: &str, config: &LanguageConfig) {\n        self.languages\n            .lock()\n            .unwrap()\n            .insert(lang.to_string().into(), config.clone());\n    }\n\n    /// Returns a list of all registered language names.\n    pub fn languages(&self) -> Vec<SharedString> {\n        self.languages.lock().unwrap().keys().cloned().collect()\n    }\n\n    /// Returns the language configuration for the given language name.\n    pub fn language(&self, name: &str) -> Option<LanguageConfig> {\n        // Try to get by name first, there may have a custom language registered\n        // Then try to get built-in language to support short language names, e.g. \"js\" for \"javascript\"\n        let languages = self.languages.lock().unwrap();\n        languages\n            .get(name)\n            .or_else(|| languages.get(Language::from_str(name).name()))\n            .cloned()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::highlighter::LanguageConfig;\n\n    #[test]\n    fn test_registry() {\n        use super::LanguageRegistry;\n        let registry = LanguageRegistry::singleton();\n\n        registry.register(\n            \"foo\",\n            &LanguageConfig::new(\"foo\", tree_sitter_json::LANGUAGE.into(), vec![], \"\", \"\", \"\"),\n        );\n\n        assert!(registry.language(\"foo\").is_some());\n        assert!(registry.language(\"rust\").is_some());\n        assert!(registry.language(\"rs\").is_some());\n        assert!(registry.language(\"javascript\").is_some());\n        assert!(registry.language(\"js\").is_some());\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/highlighter/wasm_stub.rs",
    "content": "//! WASM stub implementation for highlighter module.\n//! Provides empty/no-op implementations since tree-sitter is not available in WASM.\n//!\n//! Note: diagnostics.rs is available in WASM, only syntax highlighting requires stubs.\n\nuse gpui::{HighlightStyle, SharedString};\nuse std::ops::Range;\nuse std::time::Duration;\n\n// Syntax highlighter stub\npub struct SyntaxHighlighter;\n\nimpl SyntaxHighlighter {\n    pub fn new(_language: impl AsRef<str>) -> Self {\n        Self\n    }\n\n    pub fn highlight(&self, _text: &ropey::Rope) -> Vec<(Range<usize>, HighlightStyle)> {\n        Vec::new()\n    }\n\n    pub fn styles(\n        &self,\n        _range: &Range<usize>,\n        _theme: &HighlightTheme,\n    ) -> Vec<(Range<usize>, HighlightStyle)> {\n        Vec::new()\n    }\n\n    pub fn update(\n        &mut self,\n        _edit: Option<crate::input::InputEdit>,\n        _text: &ropey::Rope,\n        _timeout: Option<Duration>,\n    ) -> bool {\n        // No-op in WASM\n        true\n    }\n\n    pub fn language(&self) -> &SharedString {\n        static EMPTY: SharedString = SharedString::new_static(\"\");\n        &EMPTY\n    }\n\n    pub fn text(&self) -> &ropey::Rope {\n        static EMPTY_ROPE: LazyLock<ropey::Rope> = LazyLock::new(ropey::Rope::new);\n        &EMPTY_ROPE\n    }\n\n    pub fn tree(&self) -> Option<&crate::input::Tree> {\n        None\n    }\n}\n\n// Language enum stub\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum Language {\n    Unknown,\n}\n\nimpl Language {\n    pub fn from_str(_name: &str) -> Self {\n        Language::Unknown\n    }\n\n    pub fn name(&self) -> &'static str {\n        \"unknown\"\n    }\n\n    pub fn config(&self) -> LanguageConfig {\n        LanguageConfig {\n            name: \"unknown\".into(),\n        }\n    }\n\n    pub fn all() -> impl Iterator<Item = Self> {\n        std::iter::once(Language::Unknown)\n    }\n}\n\n// Language config stub (without tree_sitter::Language)\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct LanguageConfig {\n    pub name: SharedString,\n}\n\n// Re-export theme types from registry module (which will be conditionally compiled)\n// For WASM, we create minimal stubs here\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse std::{\n    collections::HashMap,\n    sync::{LazyLock, Mutex},\n};\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum FontStyle {\n    Normal,\n    Italic,\n    Underline,\n}\n\n#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, JsonSchema, Serialize, Deserialize)]\n#[repr(u16)]\npub enum FontWeightContent {\n    Thin = 100,\n    ExtraLight = 200,\n    Light = 300,\n    Normal = 400,\n    Medium = 500,\n    Semibold = 600,\n    Bold = 700,\n    ExtraBold = 800,\n    Black = 900,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]\npub struct ThemeStyle {\n    pub color: Option<gpui::Hsla>,\n    pub font_style: Option<FontStyle>,\n    pub font_weight: Option<FontWeightContent>,\n}\n\nimpl From<ThemeStyle> for HighlightStyle {\n    fn from(style: ThemeStyle) -> Self {\n        HighlightStyle {\n            color: style.color,\n            font_weight: style.font_weight.map(|w| match w {\n                FontWeightContent::Thin => gpui::FontWeight::THIN,\n                FontWeightContent::ExtraLight => gpui::FontWeight::EXTRA_LIGHT,\n                FontWeightContent::Light => gpui::FontWeight::LIGHT,\n                FontWeightContent::Normal => gpui::FontWeight::NORMAL,\n                FontWeightContent::Medium => gpui::FontWeight::MEDIUM,\n                FontWeightContent::Semibold => gpui::FontWeight::SEMIBOLD,\n                FontWeightContent::Bold => gpui::FontWeight::BOLD,\n                FontWeightContent::ExtraBold => gpui::FontWeight::EXTRA_BOLD,\n                FontWeightContent::Black => gpui::FontWeight::BLACK,\n            }),\n            font_style: style.font_style.map(|s| match s {\n                FontStyle::Normal => gpui::FontStyle::Normal,\n                FontStyle::Italic => gpui::FontStyle::Italic,\n                FontStyle::Underline => gpui::FontStyle::Normal,\n            }),\n            ..Default::default()\n        }\n    }\n}\n\n#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]\npub struct SyntaxColors {\n    // Minimal stub - actual fields are in native registry.rs\n    // Adding commonly accessed fields to avoid compilation errors\n    #[serde(rename = \"link_text\")]\n    pub link_text: Option<ThemeStyle>,\n}\n\nimpl SyntaxColors {\n    pub fn style(&self, _name: &str) -> Option<HighlightStyle> {\n        None\n    }\n\n    pub fn style_for_index(&self, _index: usize) -> Option<HighlightStyle> {\n        None\n    }\n}\n\n#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]\npub struct StatusColors {\n    // Minimal stub\n}\n\nimpl StatusColors {\n    pub fn error(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn error_background(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn error_border(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn warning(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn warning_background(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn warning_border(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn info(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn info_background(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn info_border(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn success(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn success_background(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn success_border(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn hint(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn hint_background(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n\n    pub fn hint_border(&self, _cx: &gpui::App) -> gpui::Hsla {\n        gpui::Hsla::default()\n    }\n}\n\n#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]\npub struct HighlightThemeStyle {\n    pub editor_background: Option<gpui::Hsla>,\n    pub editor_foreground: Option<gpui::Hsla>,\n    pub editor_active_line: Option<gpui::Hsla>,\n    pub editor_line_number: Option<gpui::Hsla>,\n    pub editor_active_line_number: Option<gpui::Hsla>,\n    pub editor_invisible: Option<gpui::Hsla>,\n    #[serde(flatten)]\n    pub status: StatusColors,\n    #[serde(rename = \"syntax\")]\n    pub syntax: SyntaxColors,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]\npub struct HighlightTheme {\n    pub name: String,\n    #[serde(default)]\n    pub appearance: crate::ThemeMode,\n    pub style: HighlightThemeStyle,\n}\n\nimpl std::ops::Deref for HighlightTheme {\n    type Target = SyntaxColors;\n\n    fn deref(&self) -> &Self::Target {\n        &self.style.syntax\n    }\n}\n\nimpl HighlightTheme {\n    pub fn default_dark() -> std::sync::Arc<Self> {\n        use crate::DEFAULT_THEME_COLORS;\n        DEFAULT_THEME_COLORS[&crate::ThemeMode::Dark].1.clone()\n    }\n\n    pub fn default_light() -> std::sync::Arc<Self> {\n        use crate::DEFAULT_THEME_COLORS;\n        DEFAULT_THEME_COLORS[&crate::ThemeMode::Light].1.clone()\n    }\n}\n\n// Language registry stub\npub struct LanguageRegistry {\n    languages: Mutex<HashMap<SharedString, LanguageConfig>>,\n}\n\nimpl LanguageRegistry {\n    pub fn singleton() -> &'static LazyLock<LanguageRegistry> {\n        static INSTANCE: LazyLock<LanguageRegistry> = LazyLock::new(|| LanguageRegistry {\n            languages: Mutex::new(HashMap::new()),\n        });\n        &INSTANCE\n    }\n\n    pub fn register(&self, lang: &str, config: &LanguageConfig) {\n        self.languages\n            .lock()\n            .unwrap()\n            .insert(lang.to_string().into(), config.clone());\n    }\n\n    pub fn languages(&self) -> Vec<SharedString> {\n        self.languages.lock().unwrap().keys().cloned().collect()\n    }\n\n    pub fn language(&self, name: &str) -> Option<LanguageConfig> {\n        self.languages.lock().unwrap().get(name).cloned()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/history.rs",
    "content": "use std::fmt::Debug;\nuse instant::{Duration, Instant};\n\n/// A HistoryItem represents a single change in the history.\n/// It must implement Clone and PartialEq to be used in the History.\npub trait HistoryItem: Clone + PartialEq {\n    fn version(&self) -> usize;\n    fn set_version(&mut self, version: usize);\n}\n\n/// The History is used to keep track of changes to a model and to allow undo and redo operations.\n///\n/// This is now used in Input for undo/redo operations. You can also use this in\n/// your own models to keep track of changes, for example to track the tab\n/// history for prev/next features.\n///\n/// ## Use cases\n///\n/// - Undo/redo operations in Input\n/// - Tracking tab history for prev/next features\n#[derive(Debug)]\npub struct History<I: HistoryItem> {\n    undos: Vec<I>,\n    redos: Vec<I>,\n    last_changed_at: Instant,\n    version: usize,\n    pub(crate) ignore: bool,\n    max_undos: usize,\n    group_interval: Option<Duration>,\n    grouping: bool,\n    unique: bool,\n}\n\nimpl<I> History<I>\nwhere\n    I: HistoryItem,\n{\n    pub fn new() -> Self {\n        Self {\n            undos: Default::default(),\n            redos: Default::default(),\n            ignore: false,\n            last_changed_at: Instant::now(),\n            version: 0,\n            max_undos: 1000,\n            group_interval: None,\n            grouping: false,\n            unique: false,\n        }\n    }\n\n    /// Set the maximum number of undo steps to keep, defaults to 1000.\n    pub fn max_undos(mut self, max_undos: usize) -> Self {\n        self.max_undos = max_undos;\n        self\n    }\n\n    /// Set the history to be unique, defaults to false.\n    /// If set to true, the history will only keep unique changes.\n    pub fn unique(mut self) -> Self {\n        self.unique = true;\n        self\n    }\n\n    /// Set the interval in milliseconds to group changes, defaults to None.\n    pub fn group_interval(mut self, group_interval: Duration) -> Self {\n        self.group_interval = Some(group_interval);\n        self\n    }\n\n    /// Start grouping changes, this will prevent the version from being incremented until `end_grouping` is called.\n    pub fn start_grouping(&mut self) {\n        self.grouping = true;\n    }\n\n    /// End grouping changes, this will allow the version to be incremented again.\n    pub fn end_grouping(&mut self) {\n        self.grouping = false;\n    }\n\n    /// Increment the version number if the last change was made more than `GROUP_INTERVAL` milliseconds ago.\n    fn inc_version(&mut self) -> usize {\n        let t = Instant::now();\n        if !self.grouping && Some(self.last_changed_at.elapsed()) > self.group_interval {\n            self.version += 1;\n        }\n\n        self.last_changed_at = t;\n        self.version\n    }\n\n    /// Get the current version number.\n    pub fn version(&self) -> usize {\n        self.version\n    }\n\n    /// Push a new change to the history.\n    pub fn push(&mut self, item: I) {\n        let version = self.inc_version();\n\n        if self.undos.len() >= self.max_undos {\n            self.undos.remove(0);\n        }\n\n        if self.unique {\n            self.undos.retain(|c| *c != item);\n            self.redos.retain(|c| *c != item);\n        }\n\n        let mut item = item;\n        item.set_version(version);\n        self.undos.push(item);\n    }\n\n    /// Get the undo stack.\n    pub fn undos(&self) -> &Vec<I> {\n        &self.undos\n    }\n\n    /// Get the redo stack.\n    pub fn redos(&self) -> &Vec<I> {\n        &self.redos\n    }\n\n    /// Clear the undo and redo stacks.\n    pub fn clear(&mut self) {\n        self.undos.clear();\n        self.redos.clear();\n    }\n\n    /// Undo the last change and return the changes that were undone.\n    pub fn undo(&mut self) -> Option<Vec<I>> {\n        if let Some(first_change) = self.undos.pop() {\n            let mut changes = vec![first_change.clone()];\n            // pick the next all changes with the same version\n            while self\n                .undos\n                .iter()\n                .filter(|c| c.version() == first_change.version())\n                .count()\n                > 0\n            {\n                let change = self.undos.pop().unwrap();\n                changes.push(change);\n            }\n\n            self.redos.extend(changes.clone());\n            Some(changes)\n        } else {\n            None\n        }\n    }\n\n    /// Redo the last undone change and return the changes that were redone.\n    pub fn redo(&mut self) -> Option<Vec<I>> {\n        if let Some(first_change) = self.redos.pop() {\n            let mut changes = vec![first_change.clone()];\n            // pick the next all changes with the same version\n            while self\n                .redos\n                .iter()\n                .filter(|c| c.version() == first_change.version())\n                .count()\n                > 0\n            {\n                let change = self.redos.pop().unwrap();\n                changes.push(change);\n            }\n            self.undos.extend(changes.clone());\n            Some(changes)\n        } else {\n            None\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[derive(Clone)]\n    struct TabIndex {\n        tab_index: usize,\n        version: usize,\n    }\n\n    impl PartialEq for TabIndex {\n        fn eq(&self, other: &Self) -> bool {\n            self.tab_index == other.tab_index\n        }\n    }\n\n    impl From<usize> for TabIndex {\n        fn from(value: usize) -> Self {\n            TabIndex {\n                tab_index: value,\n                version: 0,\n            }\n        }\n    }\n\n    impl HistoryItem for TabIndex {\n        fn version(&self) -> usize {\n            self.version\n        }\n        fn set_version(&mut self, version: usize) {\n            self.version = version;\n        }\n    }\n\n    #[test]\n    fn test_history() {\n        let mut history: History<TabIndex> = History::new().max_undos(100);\n        history.push(0.into());\n        history.push(3.into());\n        history.push(2.into());\n        history.push(1.into());\n\n        assert_eq!(history.version(), 4);\n        let changes = history.undo().unwrap();\n        assert_eq!(changes.len(), 1);\n        assert_eq!(changes[0].tab_index, 1);\n\n        let changes = history.undo().unwrap();\n        assert_eq!(changes.len(), 1);\n        assert_eq!(changes[0].tab_index, 2);\n\n        history.push(5.into());\n\n        let changes = history.redo().unwrap();\n        assert_eq!(changes[0].tab_index, 2);\n\n        let changes = history.redo().unwrap();\n        assert_eq!(changes[0].tab_index, 1);\n\n        let changes = history.undo().unwrap();\n        assert_eq!(changes[0].tab_index, 1);\n\n        let changes = history.undo().unwrap();\n        assert_eq!(changes[0].tab_index, 2);\n\n        let changes = history.undo().unwrap();\n        assert_eq!(changes[0].tab_index, 5);\n\n        let changes = history.undo().unwrap();\n        assert_eq!(changes[0].tab_index, 3);\n\n        let changes = history.undo().unwrap();\n        assert_eq!(changes[0].tab_index, 0);\n\n        assert_eq!(history.undo().is_none(), true);\n    }\n\n    #[test]\n    fn test_unique_history() {\n        let mut history: History<TabIndex> = History::new().max_undos(100).unique();\n\n        // Push some items\n        history.push(0.into());\n        history.push(1.into());\n        history.push(1.into()); // Duplicate, should be ignored\n        history.push(2.into());\n        history.push(1.into()); // Duplicate, should be remove old, and add new\n\n        // Check the version and undo stack\n        assert_eq!(history.version(), 5);\n        assert_eq!(history.undos().len(), 3);\n        assert_eq!(history.undos().last().unwrap().tab_index, 1);\n\n        // Undo the last change\n        let changes = history.undo().unwrap();\n        assert_eq!(changes.len(), 1);\n        assert_eq!(changes[0].tab_index, 1);\n\n        assert_eq!(history.redos().len(), 1);\n        // Push duplicate, should be ignored\n        history.push(2.into());\n\n        assert_eq!(history.undos().len(), 2);\n        assert_eq!(history.redos().len(), 1);\n\n        // Redo the last undone change\n        let changes = history.redo().unwrap();\n        assert_eq!(changes.len(), 1);\n        assert_eq!(changes[0].tab_index, 1);\n\n        // Push another item\n        history.push(3.into());\n\n        // Check the version and undo stack\n        assert_eq!(history.version(), 7);\n        assert_eq!(history.undos().len(), 4);\n\n        // Undo all changes\n        for _ in 0..4 {\n            history.undo();\n        }\n\n        // Check the undo stack is empty and redo stack has all changes\n        assert_eq!(history.undos().len(), 0);\n        assert_eq!(history.redos().len(), 4);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/hover_card.rs",
    "content": "use gpui::{\n    AnyElement, App, Bounds, Context, ElementId, InteractiveElement as _, IntoElement,\n    ParentElement, Pixels, Render, RenderOnce, StatefulInteractiveElement, StyleRefinement, Styled,\n    Task, Window, div, prelude::FluentBuilder as _,\n};\nuse std::rc::Rc;\nuse instant::Duration;\n\nuse crate::{Anchor, ElementExt, StyledExt as _, popover::Popover};\n\n/// A hover card element that displays content when hovering over a trigger element.\n///\n/// Similar to Popover but triggered by mouse hover instead of click, with configurable delays\n/// for showing and hiding the content.\n#[derive(IntoElement)]\npub struct HoverCard {\n    id: ElementId,\n    style: StyleRefinement,\n    anchor: Anchor,\n    trigger: Option<Box<dyn FnOnce(&mut Window, &App) -> AnyElement + 'static>>,\n    content: Option<\n        Rc<\n            dyn Fn(&mut HoverCardState, &mut Window, &mut Context<HoverCardState>) -> AnyElement\n                + 'static,\n        >,\n    >,\n    children: Vec<AnyElement>,\n    open_delay: Duration,\n    close_delay: Duration,\n    appearance: bool,\n    on_open_change: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,\n}\n\nimpl HoverCard {\n    /// Create a new HoverCard.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default(),\n            anchor: Anchor::TopCenter,\n            trigger: None,\n            content: None,\n            children: vec![],\n            open_delay: Duration::from_secs_f64(0.6),\n            close_delay: Duration::from_secs_f64(0.3),\n            appearance: true,\n            on_open_change: None,\n        }\n    }\n\n    /// Set the anchor corner of the hover card, default is [`Anchor::TopCenter`].\n    pub fn anchor(mut self, anchor: impl Into<Anchor>) -> Self {\n        self.anchor = anchor.into();\n        self\n    }\n\n    /// Set the trigger element of the hover card.\n    pub fn trigger<T>(mut self, trigger: T) -> Self\n    where\n        T: IntoElement + 'static,\n    {\n        self.trigger = Some(Box::new(|_, _| trigger.into_any_element()));\n        self\n    }\n\n    /// Set the content builder of the hover card.\n    ///\n    /// The builder function receives the HoverCardState, Window, and Context as parameters.\n    pub fn content<F, E>(mut self, content: F) -> Self\n    where\n        F: Fn(&mut HoverCardState, &mut Window, &mut Context<HoverCardState>) -> E + 'static,\n        E: IntoElement + 'static,\n    {\n        self.content = Some(Rc::new(move |state, window, cx| {\n            content(state, window, cx).into_any_element()\n        }));\n        self\n    }\n\n    /// Set the delay before showing the hover card in milliseconds, default is 600ms.\n    pub fn open_delay(mut self, duration: Duration) -> Self {\n        self.open_delay = duration;\n        self\n    }\n\n    /// Set the delay before hiding the hover card in milliseconds, default is 300ms.\n    pub fn close_delay(mut self, duration: Duration) -> Self {\n        self.close_delay = duration;\n        self\n    }\n\n    /// Set whether to apply default appearance styles, default is `true`.\n    pub fn appearance(mut self, appearance: bool) -> Self {\n        self.appearance = appearance;\n        self\n    }\n\n    /// Set a callback to be called when the open state changes.\n    pub fn on_open_change<F>(mut self, callback: F) -> Self\n    where\n        F: Fn(&bool, &mut Window, &mut App) + 'static,\n    {\n        self.on_open_change = Some(Rc::new(callback));\n        self\n    }\n}\n\nimpl Styled for HoverCard {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl ParentElement for HoverCard {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\n/// State management for HoverCard component.\npub struct HoverCardState {\n    open: bool,\n    trigger_bounds: Bounds<Pixels>,\n    open_delay: Duration,\n    close_delay: Duration,\n\n    // Timer management\n    open_task: Option<Task<()>>,\n    close_task: Option<Task<()>>,\n    epoch: usize, // Used to cancel stale timers\n\n    // Hover state tracking\n    is_hovering_trigger: bool,\n    is_hovering_content: bool,\n\n    // Callbacks\n    on_open_change: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,\n}\n\nimpl HoverCardState {\n    fn new(open_delay: Duration, close_delay: Duration) -> Self {\n        Self {\n            open: false,\n            trigger_bounds: Bounds::default(),\n            open_delay,\n            close_delay,\n            open_task: None,\n            close_task: None,\n            epoch: 0,\n            is_hovering_trigger: false,\n            is_hovering_content: false,\n            on_open_change: None,\n        }\n    }\n\n    /// Check if the hover card is open.\n    pub fn is_open(&self) -> bool {\n        self.open\n    }\n\n    /// Schedule opening the hover card after the configured delay.\n    fn schedule_open(&mut self, cx: &mut Context<Self>) {\n        self.cancel_tasks();\n        let epoch = self.next_epoch();\n        let delay = self.open_delay;\n\n        self.open_task = Some(cx.spawn(async move |this, cx| {\n            cx.background_executor().timer(delay).await;\n\n            let _ = this.update(cx, |state, cx| {\n                if state.epoch == epoch {\n                    state.set_open(true, cx);\n                }\n            });\n        }));\n    }\n\n    /// Schedule closing the hover card after the configured delay.\n    fn schedule_close(&mut self, cx: &mut Context<Self>) {\n        self.cancel_tasks();\n        let epoch = self.next_epoch();\n        let delay = self.close_delay;\n\n        self.close_task = Some(cx.spawn(async move |this, cx| {\n            cx.background_executor().timer(delay).await;\n\n            let _ = this.update(cx, |state, cx| {\n                if state.epoch == epoch && !state.is_hovering_trigger && !state.is_hovering_content\n                {\n                    state.set_open(false, cx);\n                }\n            });\n        }));\n    }\n\n    fn cancel_tasks(&mut self) {\n        self.epoch += 1; // Invalidate all pending timers\n        self.open_task = None;\n        self.close_task = None;\n    }\n\n    fn next_epoch(&mut self) -> usize {\n        self.epoch += 1;\n        self.epoch\n    }\n\n    fn set_open(&mut self, open: bool, cx: &mut Context<Self>) {\n        if self.open == open {\n            return;\n        }\n\n        self.open = open;\n        cx.notify();\n    }\n\n    /// Handle hover state change on the trigger element.\n    fn on_trigger_hover(&mut self, hovering: bool, cx: &mut Context<Self>) {\n        self.is_hovering_trigger = hovering;\n\n        if hovering {\n            self.schedule_open(cx);\n        } else {\n            // Only close if not hovering content\n            if !self.is_hovering_content {\n                self.schedule_close(cx);\n            }\n        }\n    }\n\n    /// Handle hover state change on the content element.\n    fn on_content_hover(&mut self, hovered: bool, cx: &mut Context<Self>) {\n        self.is_hovering_content = hovered;\n\n        if hovered {\n            self.cancel_tasks();\n        } else {\n            // Only close if not hovering trigger\n            if !self.is_hovering_trigger {\n                self.schedule_close(cx);\n            }\n        }\n    }\n}\n\nimpl Render for HoverCardState {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        div() // Empty render\n    }\n}\n\nimpl RenderOnce for HoverCard {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let state = window.use_keyed_state(self.id.clone(), cx, |_, _| {\n            HoverCardState::new(self.open_delay, self.close_delay)\n        });\n\n        // Update state and track if controlled mode changed the open state\n        let prev_open = state.read(cx).open;\n        state.update(cx, |state, _| {\n            state.open_delay = self.open_delay;\n            state.close_delay = self.close_delay;\n            state.on_open_change = self.on_open_change.clone();\n        });\n\n        let open = state.read(cx).open;\n        let trigger_bounds = state.read(cx).trigger_bounds;\n\n        // Trigger callback if state changed in controlled mode\n        if prev_open != open {\n            if let Some(ref callback) = self.on_open_change {\n                callback(&open, window, cx);\n            }\n        }\n\n        let Some(trigger) = self.trigger else {\n            return div().id(\"empty\");\n        };\n\n        let root = div().id(self.id).child(\n            div()\n                .id(\"trigger\")\n                .child((trigger)(window, cx))\n                .on_hover(window.listener_for(&state, |state, hovered, _, cx| {\n                    state.on_trigger_hover(*hovered, cx);\n                }))\n                .on_prepaint({\n                    let state = state.clone();\n                    move |bounds, _, cx| {\n                        state.update(cx, |state, _| {\n                            state.trigger_bounds = bounds;\n                        });\n                    }\n                }),\n        );\n\n        if !open {\n            return root;\n        }\n\n        let popover_content =\n            Popover::render_popover_content(self.anchor, self.appearance, window, cx)\n                .overflow_hidden()\n                .on_hover(window.listener_for(&state, |state, hovered, _, cx| {\n                    state.on_content_hover(*hovered, cx);\n                }))\n                .when_some(self.content, |this, content| {\n                    this.child(state.update(cx, |state, cx| (content)(state, window, cx)))\n                })\n                .children(self.children)\n                .refine_style(&self.style);\n\n        root.child(Popover::render_popover(\n            self.anchor,\n            trigger_bounds,\n            popover_content,\n            window,\n            cx,\n        ))\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/icon.rs",
    "content": "use crate::{ActiveTheme, Sizable, Size};\nuse gpui::{\n    AnyElement, App, AppContext, Context, Entity, Hsla, IntoElement, Radians, Render, RenderOnce,\n    SharedString, StyleRefinement, Styled, Svg, Transformation, Window,\n    prelude::FluentBuilder as _, svg,\n};\nuse gpui_component_macros::icon_named;\n\n/// Types implementing this trait can automatically be converted to [`Icon`].\n///\n/// This allows you to implement a custom version of [`IconName`] that functions as a drop-in\n/// replacement for other UI components.\npub trait IconNamed {\n    /// Returns the embedded path of the icon.\n    fn path(self) -> SharedString;\n}\n\nimpl<T: IconNamed> From<T> for Icon {\n    fn from(value: T) -> Self {\n        Icon::build(value)\n    }\n}\n\nicon_named!(IconName, \"../assets/assets/icons\");\n\nimpl IconName {\n    /// Return the icon as a Entity<Icon>\n    pub fn view(self, cx: &mut App) -> Entity<Icon> {\n        Icon::build(self).view(cx)\n    }\n}\n\nimpl From<IconName> for AnyElement {\n    fn from(val: IconName) -> Self {\n        Icon::build(val).into_any_element()\n    }\n}\n\nimpl RenderOnce for IconName {\n    fn render(self, _: &mut Window, _cx: &mut App) -> impl IntoElement {\n        Icon::build(self)\n    }\n}\n\n#[derive(IntoElement)]\npub struct Icon {\n    base: Svg,\n    style: StyleRefinement,\n    path: SharedString,\n    text_color: Option<Hsla>,\n    size: Option<Size>,\n    rotation: Option<Radians>,\n}\n\nimpl Default for Icon {\n    fn default() -> Self {\n        Self {\n            base: svg().flex_none().size_4(),\n            style: StyleRefinement::default(),\n            path: \"\".into(),\n            text_color: None,\n            size: None,\n            rotation: None,\n        }\n    }\n}\n\nimpl Clone for Icon {\n    fn clone(&self) -> Self {\n        let mut this = Self::default().path(self.path.clone());\n        this.style = self.style.clone();\n        this.rotation = self.rotation;\n        this.size = self.size;\n        this.text_color = self.text_color;\n        this\n    }\n}\n\nimpl Icon {\n    pub fn new(icon: impl Into<Icon>) -> Self {\n        icon.into()\n    }\n\n    fn build(name: impl IconNamed) -> Self {\n        Self::default().path(name.path())\n    }\n\n    /// Set the icon path of the Assets bundle\n    ///\n    /// For example: `icons/foo.svg`\n    pub fn path(mut self, path: impl Into<SharedString>) -> Self {\n        self.path = path.into();\n        self\n    }\n\n    /// Create a new view for the icon\n    pub fn view(self, cx: &mut App) -> Entity<Icon> {\n        cx.new(|_| self)\n    }\n\n    pub fn transform(mut self, transformation: gpui::Transformation) -> Self {\n        self.base = self.base.with_transformation(transformation);\n        self\n    }\n\n    pub fn empty() -> Self {\n        Self::default()\n    }\n\n    /// Rotate the icon by the given angle\n    pub fn rotate(mut self, radians: impl Into<Radians>) -> Self {\n        self.base = self\n            .base\n            .with_transformation(Transformation::rotate(radians));\n        self\n    }\n}\n\nimpl Styled for Icon {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n\n    fn text_color(mut self, color: impl Into<Hsla>) -> Self {\n        self.text_color = Some(color.into());\n        self\n    }\n}\n\nimpl Sizable for Icon {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = Some(size.into());\n        self\n    }\n}\n\nimpl RenderOnce for Icon {\n    fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {\n        let text_color = self.text_color.unwrap_or_else(|| window.text_style().color);\n        let text_size = window.text_style().font_size.to_pixels(window.rem_size());\n        let has_base_size = self.style.size.width.is_some() || self.style.size.height.is_some();\n\n        let mut base = self.base;\n        *base.style() = self.style;\n\n        base.flex_shrink_0()\n            .text_color(text_color)\n            .when(!has_base_size, |this| this.size(text_size))\n            .when_some(self.size, |this, size| match size {\n                Size::Size(px) => this.size(px),\n                Size::XSmall => this.size_3(),\n                Size::Small => this.size_3p5(),\n                Size::Medium => this.size_4(),\n                Size::Large => this.size_6(),\n            })\n            .path(self.path)\n    }\n}\n\nimpl From<Icon> for AnyElement {\n    fn from(val: Icon) -> Self {\n        val.into_any_element()\n    }\n}\n\nimpl Render for Icon {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let text_color = self.text_color.unwrap_or_else(|| cx.theme().foreground);\n        let text_size = window.text_style().font_size.to_pixels(window.rem_size());\n        let has_base_size = self.style.size.width.is_some() || self.style.size.height.is_some();\n\n        let mut base = svg().flex_none();\n        *base.style() = self.style.clone();\n\n        base.flex_shrink_0()\n            .text_color(text_color)\n            .when(!has_base_size, |this| this.size(text_size))\n            .when_some(self.size, |this, size| match size {\n                Size::Size(px) => this.size(px),\n                Size::XSmall => this.size_3(),\n                Size::Small => this.size_3p5(),\n                Size::Medium => this.size_4(),\n                Size::Large => this.size_6(),\n            })\n            .path(self.path.clone())\n            .when_some(self.rotation, |this, rotation| {\n                this.with_transformation(Transformation::rotate(rotation))\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/index_path.rs",
    "content": "use std::fmt::{Debug, Display};\n\nuse gpui::ElementId;\n\n/// Represents an index path in a list, which consists of a section index,\n///\n/// The default values for section, row, and column are all set to 0.\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]\npub struct IndexPath {\n    /// The section index.\n    pub section: usize,\n    /// The item index in the section.\n    pub row: usize,\n    /// The column index.\n    pub column: usize,\n}\n\nimpl From<IndexPath> for ElementId {\n    fn from(path: IndexPath) -> Self {\n        ElementId::Name(format!(\"index-path({},{},{})\", path.section, path.row, path.column).into())\n    }\n}\n\nimpl Display for IndexPath {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"IndexPath(section: {}, row: {}, column: {})\",\n            self.section, self.row, self.column\n        )\n    }\n}\n\nimpl IndexPath {\n    /// Create a new index path with the specified section and row.\n    ///\n    /// The `section` is set to 0 by default.\n    /// The `column` is set to 0 by default.\n    pub fn new(row: usize) -> Self {\n        IndexPath {\n            section: 0,\n            row,\n            ..Default::default()\n        }\n    }\n\n    /// Set the section for the index path.\n    pub fn section(mut self, section: usize) -> Self {\n        self.section = section;\n        self\n    }\n\n    /// Set the row for the index path.\n    pub fn row(mut self, row: usize) -> Self {\n        self.row = row;\n        self\n    }\n\n    /// Set the column for the index path.\n    pub fn column(mut self, column: usize) -> Self {\n        self.column = column;\n        self\n    }\n\n    /// Check if the self is equal to the given index path (Same section and row).\n    pub fn eq_row(&self, index: IndexPath) -> bool {\n        self.section == index.section && self.row == index.row\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_into_element_id() {\n        let index_path = IndexPath::new(2).section(1).column(3);\n        let element_id: ElementId = index_path.into();\n        assert_eq!(element_id.to_string(), \"index-path(1,2,3)\");\n    }\n\n    #[test]\n    fn test_display() {\n        assert_eq!(\n            format!(\"{}\", IndexPath::new(2).section(1).column(3)),\n            \"IndexPath(section: 1, row: 2, column: 3)\"\n        );\n    }\n\n    #[test]\n    fn test_index_path() {\n        let mut index_path = IndexPath::default();\n        assert_eq!(index_path.section, 0);\n        assert_eq!(index_path.row, 0);\n        assert_eq!(index_path.column, 0);\n\n        index_path = index_path.section(1).row(2).column(3);\n        assert_eq!(index_path.section, 1);\n        assert_eq!(index_path.row, 2);\n        assert_eq!(index_path.column, 3);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/blink_cursor.rs",
    "content": "use instant::Duration;\nuse gpui::{Context, Pixels, Task, px};\n\nstatic INTERVAL: Duration = Duration::from_millis(500);\nstatic PAUSE_DELAY: Duration = Duration::from_millis(300);\n\n// On Windows, Linux, we should use integer to avoid blurry cursor.\n#[cfg(not(target_os = \"macos\"))]\npub(super) const CURSOR_WIDTH: Pixels = px(2.);\n#[cfg(target_os = \"macos\")]\npub(super) const CURSOR_WIDTH: Pixels = px(1.5);\n\n/// To manage the Input cursor blinking.\n///\n/// It will start blinking with a interval of 500ms.\n/// Every loop will notify the view to update the `visible`, and Input will observe this update to touch repaint.\n///\n/// The input painter will check if this in visible state, then it will draw the cursor.\npub(crate) struct BlinkCursor {\n    visible: bool,\n    paused: bool,\n    epoch: usize,\n\n    _task: Task<()>,\n}\n\nimpl BlinkCursor {\n    pub fn new() -> Self {\n        Self {\n            visible: false,\n            paused: false,\n            epoch: 0,\n            _task: Task::ready(()),\n        }\n    }\n\n    /// Start the blinking\n    pub fn start(&mut self, cx: &mut Context<Self>) {\n        self.blink(self.epoch, cx);\n    }\n\n    pub fn stop(&mut self, cx: &mut Context<Self>) {\n        self.epoch = 0;\n        cx.notify();\n    }\n\n    fn next_epoch(&mut self) -> usize {\n        self.epoch += 1;\n        self.epoch\n    }\n\n    fn blink(&mut self, epoch: usize, cx: &mut Context<Self>) {\n        if self.paused || epoch != self.epoch {\n            self.visible = true;\n            return;\n        }\n\n        self.visible = !self.visible;\n        cx.notify();\n\n        // Schedule the next blink\n        let epoch = self.next_epoch();\n        self._task = cx.spawn(async move |this, cx| {\n            cx.background_executor().timer(INTERVAL).await;\n            if let Some(this) = this.upgrade() {\n                this.update(cx, |this, cx| this.blink(epoch, cx));\n            }\n        });\n    }\n\n    pub fn visible(&self) -> bool {\n        // Keep showing the cursor if paused\n        self.paused || self.visible\n    }\n\n    /// Pause the blinking, and delay 500ms to resume the blinking.\n    pub fn pause(&mut self, cx: &mut Context<Self>) {\n        self.paused = true;\n        self.visible = true;\n        cx.notify();\n\n        // delay 500ms to start the blinking\n        let epoch = self.next_epoch();\n        self._task = cx.spawn(async move |this, cx| {\n            cx.background_executor().timer(PAUSE_DELAY).await;\n\n            if let Some(this) = this.upgrade() {\n                this.update(cx, |this, cx| {\n                    this.paused = false;\n                    this.blink(epoch, cx);\n                });\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/change.rs",
    "content": "use std::fmt::Debug;\n\nuse crate::{history::HistoryItem, input::Selection};\n\n#[derive(Debug, PartialEq, Clone)]\npub struct Change {\n    pub(crate) old_range: Selection,\n    pub(crate) old_text: String,\n    pub(crate) new_range: Selection,\n    pub(crate) new_text: String,\n    version: usize,\n}\n\nimpl Change {\n    pub fn new(\n        old_range: impl Into<Selection>,\n        old_text: &str,\n        new_range: impl Into<Selection>,\n        new_text: &str,\n    ) -> Self {\n        Self {\n            old_range: old_range.into(),\n            old_text: old_text.to_string(),\n            new_range: new_range.into(),\n            new_text: new_text.to_string(),\n            version: 0,\n        }\n    }\n}\n\nimpl HistoryItem for Change {\n    fn version(&self) -> usize {\n        self.version\n    }\n\n    fn set_version(&mut self, version: usize) {\n        self.version = version;\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/clear_button.rs",
    "content": "use gpui::{App, Styled};\n\nuse crate::{\n    button::{Button, ButtonVariants as _},\n    ActiveTheme as _, Icon, IconName, Sizable as _,\n};\n\n#[inline]\npub(crate) fn clear_button(cx: &App) -> Button {\n    Button::new(\"clean\")\n        .icon(Icon::new(IconName::CircleX))\n        .ghost()\n        .xsmall()\n        .tab_stop(false)\n        .text_color(cx.theme().muted_foreground)\n}\n"
  },
  {
    "path": "crates/ui/src/input/cursor.rs",
    "content": "use std::ops::{Range, RangeBounds};\n\n/// A selection in the text, represented by start and end byte indices.\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]\npub struct Selection {\n    pub start: usize,\n    pub end: usize,\n}\n\nimpl Selection {\n    pub fn new(start: usize, end: usize) -> Self {\n        Self { start, end }\n    }\n\n    pub fn len(&self) -> usize {\n        self.end.saturating_sub(self.start)\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.start == self.end\n    }\n\n    /// Clears the selection, setting start and end to 0.\n    pub fn clear(&mut self) {\n        self.start = 0;\n        self.end = 0;\n    }\n\n    /// Checks if the given offset is within the selection range.\n    pub fn contains(&self, offset: usize) -> bool {\n        offset >= self.start && offset < self.end\n    }\n}\n\nimpl From<Range<usize>> for Selection {\n    fn from(value: Range<usize>) -> Self {\n        Self::new(value.start, value.end)\n    }\n}\nimpl From<Selection> for Range<usize> {\n    fn from(value: Selection) -> Self {\n        value.start..value.end\n    }\n}\nimpl RangeBounds<usize> for Selection {\n    fn start_bound(&self) -> std::ops::Bound<&usize> {\n        std::ops::Bound::Included(&self.start)\n    }\n\n    fn end_bound(&self) -> std::ops::Bound<&usize> {\n        std::ops::Bound::Excluded(&self.end)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::input::Position;\n\n    #[test]\n    fn test_line_column_from_to() {\n        assert_eq!(\n            Position::new(1, 2),\n            Position {\n                line: 1,\n                character: 2\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/display_map/README.md",
    "content": "# Display Mapping System\n\nLayered coordinate conversion and code folding for Editor/Input.\n\n## Architecture\n\n```\nBuffer (Rope)              Logical text\n    ↓\nWrapMap                    Soft-wrapping (buffer_line ↔ wrap_row)\n    ↓\nFoldMap                    Fold projection (wrap_row ↔ display_row)\n    ↓\nDisplayMap                 Public facade (BufferPos ↔ DisplayPos)\n```\n\n## Coordinate Systems\n\n| Type | Fields | Scope | Description |\n|------|--------|-------|-------------|\n| `BufferPos` | `{ line, col }` | public | Logical line/column in Rope |\n| `WrapPos` | `{ row, col }` | internal | Visual row after soft-wrapping |\n| `DisplayPos` | `{ row, col }` | public | Final visible row after folding |\n\n## Modules\n\n### `DisplayMap` — Public facade\n\n- `buffer_pos_to_display_pos()` / `display_pos_to_buffer_pos()`\n- `set_fold_candidates()`, `toggle_fold()`, `is_folded_at()`, `clear_folds()`\n- `on_text_changed()`, `on_layout_changed()`, `set_font()`\n- `adjust_folds_for_edit()` — incremental fold/candidate line-delta adjustment\n- `update_fold_candidates_for_edit()` — region-scoped candidate extraction after edits\n\n### `WrapMap` — Soft-wrapping layer\n\nBuilt on `TextWrapper`. Provides buffer ↔ wrap coordinate mapping with prefix sum cache for O(1) line lookups.\n\n### `FoldMap` — Fold projection layer\n\nMaintains `visible_wrap_rows` and reverse mapping. When no folds are active, uses identity mapping (wrap_row == display_row) without Vec allocation.\n\n### `FoldRange` / `folding` — Fold extraction\n\n- `extract_fold_ranges(tree)` — full tree traversal (initial load only)\n- `extract_fold_ranges_in_range(tree, byte_range)` — region-scoped traversal for edits, skips subtrees outside range\n"
  },
  {
    "path": "crates/ui/src/input/display_map/display_map.rs",
    "content": "/// DisplayMap: Public facade for Editor/Input display mapping.\n///\n/// This combines WrapMap and FoldMap to provide a unified API:\n/// - BufferPoint ↔ DisplayPoint conversion\n/// - Fold management (candidates, toggle, query)\n/// - Automatic projection updates on text/layout changes\nuse std::ops::Range;\n\nuse gpui::{App, Font, Pixels};\nuse ropey::Rope;\n\nuse super::fold_map::FoldMap;\nuse super::folding::FoldRange;\nuse super::text_wrapper::{LineItem, WrapDisplayPoint};\nuse super::wrap_map::WrapMap;\nuse super::{BufferPoint, DisplayPoint};\nuse crate::input::display_map::WrapPoint;\nuse crate::input::rope_ext::RopeExt as _;\nuse crate::input::Point as TreeSitterPoint;\n\n/// DisplayMap is the main interface for Editor/Input coordinate mapping.\n///\n/// It manages the two-layer projection:\n/// 1. Buffer → Wrap (soft-wrapping)\n/// 2. Wrap → Display (folding)\n///\n/// Editor/Input only needs to work with BufferPoint and DisplayPoint.\npub struct DisplayMap {\n    wrap_map: WrapMap,\n    fold_map: FoldMap,\n}\n\nimpl DisplayMap {\n    pub fn new(font: Font, font_size: Pixels, wrap_width: Option<Pixels>) -> Self {\n        Self {\n            wrap_map: WrapMap::new(font, font_size, wrap_width),\n            fold_map: FoldMap::new(),\n        }\n    }\n\n    // ==================== Core Coordinate Mapping ====================\n\n    /// Convert buffer position to display position\n    pub fn buffer_pos_to_display_pos(&self, pos: BufferPoint) -> DisplayPoint {\n        // Buffer → Wrap\n        let wrap_pos = self.wrap_map.buffer_pos_to_wrap_pos(pos);\n\n        // Wrap → Display\n        if let Some(display_row) = self.fold_map.wrap_row_to_display_row(wrap_pos.row) {\n            DisplayPoint::new(display_row, wrap_pos.col)\n        } else {\n            // Cursor is in a folded region, find nearest visible row\n            let display_row = self.fold_map.nearest_visible_display_row(wrap_pos.row);\n            DisplayPoint::new(display_row, 0) // Column 0 at fold boundary\n        }\n    }\n\n    /// Convert display position to buffer position\n    pub fn display_pos_to_buffer_pos(&self, pos: DisplayPoint) -> BufferPoint {\n        // Display → Wrap\n        let wrap_row = self.fold_map.display_row_to_wrap_row(pos.row).unwrap_or(0);\n\n        // Wrap → Buffer\n        let wrap_pos = WrapPoint::new(wrap_row, pos.col);\n        self.wrap_map.wrap_pos_to_buffer_pos(wrap_pos)\n    }\n\n    /// Get total number of visible display rows\n    #[inline]\n    pub fn display_row_count(&self) -> usize {\n        self.fold_map.display_row_count()\n    }\n\n    /// Get the buffer line for a given display row\n    pub fn display_row_to_buffer_line(&self, display_row: usize) -> usize {\n        // Display → Wrap\n        let wrap_row = self\n            .fold_map\n            .display_row_to_wrap_row(display_row)\n            .unwrap_or(0);\n\n        // Wrap → Buffer line\n        self.wrap_map.wrap_row_to_buffer_line(wrap_row)\n    }\n\n    /// Get the display row range for a buffer line: [start, end)\n    /// Returns None if the buffer line is completely hidden\n    pub fn buffer_line_to_display_row_range(&self, line: usize) -> Option<Range<usize>> {\n        // Buffer line → Wrap row range\n        let wrap_row_range = self.wrap_map.buffer_line_to_wrap_row_range(line);\n\n        // Find first and last visible display rows in this range\n        let mut first_display_row = None;\n        let mut last_display_row = None;\n\n        for wrap_row in wrap_row_range {\n            if let Some(display_row) = self.fold_map.wrap_row_to_display_row(wrap_row) {\n                if first_display_row.is_none() {\n                    first_display_row = Some(display_row);\n                }\n                last_display_row = Some(display_row);\n            }\n        }\n\n        if let (Some(start), Some(end)) = (first_display_row, last_display_row) {\n            Some(start..end + 1)\n        } else {\n            None // Completely folded\n        }\n    }\n\n    /// Check if a buffer line is completely hidden\n    #[inline]\n    pub fn is_buffer_line_hidden(&self, line: usize) -> bool {\n        self.buffer_line_to_display_row_range(line).is_none()\n    }\n\n    /// Set fold candidates (from tree-sitter/LSP)\n    pub fn set_fold_candidates(&mut self, candidates: Vec<FoldRange>) {\n        self.fold_map.set_candidates(candidates);\n        self.rebuild_fold_projection();\n    }\n\n    /// Set a fold at the given start_line (must be in candidates)\n    pub fn set_folded(&mut self, start_line: usize, folded: bool) {\n        self.fold_map.set_folded(start_line, folded);\n        self.rebuild_fold_projection();\n    }\n\n    /// Toggle fold at the given start_line\n    pub fn toggle_fold(&mut self, start_line: usize) {\n        self.fold_map.toggle_fold(start_line);\n        self.rebuild_fold_projection();\n    }\n\n    /// Check if a line is currently folded\n    #[inline]\n    pub fn is_folded_at(&self, start_line: usize) -> bool {\n        self.fold_map.is_folded_at(start_line)\n    }\n\n    /// Check if a line is a fold candidate\n    #[inline]\n    pub fn is_fold_candidate(&self, start_line: usize) -> bool {\n        self.fold_map.is_fold_candidate(start_line)\n    }\n\n    /// Get all currently folded ranges\n    #[inline]\n    pub fn folded_ranges(&self) -> &[FoldRange] {\n        self.fold_map.folded_ranges()\n    }\n\n    /// Clear all folds\n    pub fn clear_folds(&mut self) {\n        self.fold_map.clear_folds();\n        self.rebuild_fold_projection();\n    }\n\n    // ==================== Text and Layout Updates ====================\n\n    /// Adjust folds and candidates for a text edit before updating the wrap map.\n    ///\n    /// Must be called with the OLD text (before replacement) and the edit range/new_text\n    /// so we can compute which old lines were affected.\n    pub fn adjust_folds_for_edit(&mut self, old_text: &Rope, range: &Range<usize>, new_text: &str) {\n        if self.fold_map.folded_ranges().is_empty() && self.fold_map.fold_candidates().is_empty() {\n            return;\n        }\n\n        let edit_start_line = old_text.offset_to_point(range.start).row;\n        let edit_end_line = old_text.offset_to_point(range.end.min(old_text.len())).row;\n\n        let old_lines_in_range = edit_end_line.saturating_sub(edit_start_line);\n        let new_lines_in_range = new_text.chars().filter(|c| *c == '\\n').count();\n        let line_delta = new_lines_in_range as isize - old_lines_in_range as isize;\n\n        self.fold_map\n            .adjust_folds_for_edit(edit_start_line, edit_end_line, line_delta);\n    }\n\n    /// Incrementally update fold candidates after a text edit.\n    ///\n    /// Extracts new fold candidates only within the edited byte range\n    /// and merges them with existing (already adjusted) candidates.\n    pub fn update_fold_candidates_for_edit(\n        &mut self,\n        tree: &super::folding::Tree,\n        edit_byte_range: Range<usize>,\n        new_text: &Rope,\n    ) {\n        let new_start_line = new_text.offset_to_point(edit_byte_range.start).row;\n        let new_end_line = new_text\n            .offset_to_point(edit_byte_range.end.min(new_text.len()))\n            .row;\n\n        let new_candidates = super::folding::extract_fold_ranges_in_range(tree, edit_byte_range);\n        self.fold_map\n            .merge_candidates_for_edit(new_start_line, new_end_line, new_candidates);\n    }\n\n    /// Update text (incremental or full)\n    pub fn on_text_changed(\n        &mut self,\n        changed_text: &Rope,\n        range: &Range<usize>,\n        new_text: &Rope,\n        cx: &mut App,\n    ) {\n        self.wrap_map\n            .on_text_changed(changed_text, range, new_text, cx);\n        self.rebuild_fold_projection();\n    }\n\n    /// Update layout parameters (wrap width or font)\n    pub fn on_layout_changed(&mut self, wrap_width: Option<Pixels>, cx: &mut App) {\n        self.wrap_map.on_layout_changed(wrap_width, cx);\n        self.rebuild_fold_projection();\n    }\n\n    /// Set font parameters\n    pub fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) {\n        self.wrap_map.set_font(font, font_size, cx);\n        self.rebuild_fold_projection();\n    }\n\n    /// Ensure text is prepared (initializes wrapper if needed)\n    pub fn ensure_text_prepared(&mut self, text: &Rope, cx: &mut App) {\n        let did_initialize = self.wrap_map.ensure_text_prepared(text, cx);\n        if did_initialize {\n            self.rebuild_fold_projection();\n        }\n    }\n\n    /// Initialize with text\n    pub fn set_text(&mut self, text: &Rope, cx: &mut App) {\n        self.wrap_map.set_text(text, cx);\n        self.rebuild_fold_projection();\n    }\n\n    // ==================== Internal Helpers ====================\n\n    /// Rebuild fold projection after wrap_map or fold state changes\n    /// Only rebuilds if there are actually folded ranges\n    fn rebuild_fold_projection(&mut self) {\n        if !self.fold_map.folded_ranges().is_empty() {\n            self.fold_map.rebuild(&self.wrap_map);\n        } else {\n            // No active folds: identity mapping (wrap_row == display_row).\n            // Just update cached count so query methods work without Vec allocation.\n            self.fold_map\n                .mark_dirty_with_wrap_count(self.wrap_map.wrap_row_count());\n        }\n    }\n\n    // ==================== Wrap Display Point Operations ====================\n\n    /// Convert byte offset to wrap display point (with soft wrap info).\n    #[inline]\n    pub(crate) fn offset_to_wrap_display_point(&self, offset: usize) -> WrapDisplayPoint {\n        self.wrap_map.wrapper().offset_to_display_point(offset)\n    }\n\n    /// Convert wrap display point to byte offset.\n    #[inline]\n    pub(crate) fn wrap_display_point_to_offset(&self, point: WrapDisplayPoint) -> usize {\n        self.wrap_map.wrapper().display_point_to_offset(point)\n    }\n\n    /// Convert wrap display point to TreeSitterPoint (buffer line/col).\n    #[inline]\n    pub(crate) fn wrap_display_point_to_point(\n        &self,\n        point: WrapDisplayPoint,\n    ) -> TreeSitterPoint {\n        self.wrap_map.wrapper().display_point_to_point(point)\n    }\n\n    /// Convert a wrap row to a display row (skipping folded rows).\n    /// Returns None if the wrap row is folded.\n    #[inline]\n    pub fn wrap_row_to_display_row(&self, wrap_row: usize) -> Option<usize> {\n        self.fold_map.wrap_row_to_display_row(wrap_row)\n    }\n\n    /// Find the nearest visible display row for a given wrap row.\n    #[inline]\n    pub fn nearest_visible_display_row(&self, wrap_row: usize) -> usize {\n        self.fold_map.nearest_visible_display_row(wrap_row)\n    }\n\n    /// Convert a display row to a wrap row.\n    #[inline]\n    pub fn display_row_to_wrap_row(&self, display_row: usize) -> Option<usize> {\n        self.fold_map.display_row_to_wrap_row(display_row)\n    }\n\n    /// Get the longest row index (by byte length).\n    #[inline]\n    pub(crate) fn longest_row(&self) -> usize {\n        self.wrap_map.wrapper().longest_row.row\n    }\n\n    // ==================== Access Methods ====================\n\n    /// Get access to line items (for rendering)\n    #[inline]\n    pub(crate) fn lines(&self) -> &[LineItem] {\n        self.wrap_map.lines()\n    }\n\n    /// Get the rope text\n    #[inline]\n    pub fn text(&self) -> &Rope {\n        self.wrap_map.text()\n    }\n\n    /// Calculate how many wrap rows of a buffer line are visible (not folded)\n    #[inline]\n    pub fn visible_wrap_row_count_for_buffer_line(&self, line: usize) -> usize {\n        self.wrap_map\n            .visible_wrap_row_count_for_line(line, &self.fold_map)\n    }\n\n    /// Get the wrap row count (before folding)\n    #[inline]\n    pub fn wrap_row_count(&self) -> usize {\n        self.wrap_map.wrap_row_count()\n    }\n\n    /// Get the buffer line count (logical lines)\n    #[inline]\n    pub fn buffer_line_count(&self) -> usize {\n        self.wrap_map.buffer_line_count()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/display_map/fold_map.rs",
    "content": "/// FoldMap: Folding projection layer (Wrap rows → Display rows).\n///\n/// This module manages code folding by:\n/// - Filtering out wrap rows that belong to folded regions\n/// - Maintaining bidirectional mapping: wrap_row ↔ display_row\n/// - Handling fold state changes and rebuilding the projection\nuse super::folding::FoldRange;\nuse super::wrap_map::WrapMap;\n\n/// FoldMap projects wrap rows to display rows by hiding folded regions.\npub struct FoldMap {\n    /// Mapping: display_row → wrap_row\n    /// index = display_row, value = actual wrap_row\n    visible_wrap_rows: Vec<usize>,\n\n    /// Reverse mapping: wrap_row → display_row\n    /// index = wrap_row, value = Some(display_row) if visible, None if folded\n    wrap_row_to_display_row: Vec<Option<usize>>,\n\n    /// Candidate fold ranges (from tree-sitter/LSP)\n    /// Sorted by start_line, unique start_line\n    candidates: Vec<FoldRange>,\n\n    /// Currently folded ranges\n    /// Subset of candidates, sorted by start_line\n    folded: Vec<FoldRange>,\n\n    /// Flag indicating if the fold projection needs rebuilding\n    /// Used for lazy evaluation to avoid expensive rebuilds on every text change\n    needs_rebuild: bool,\n\n    /// Cached wrap_row_count from last rebuild\n    /// Used to detect if WrapMap changed and rebuild is needed\n    cached_wrap_row_count: usize,\n}\n\nimpl FoldMap {\n    pub fn new() -> Self {\n        Self {\n            visible_wrap_rows: Vec::new(),\n            wrap_row_to_display_row: Vec::new(),\n            candidates: Vec::new(),\n            folded: Vec::new(),\n            needs_rebuild: true,\n            cached_wrap_row_count: 0,\n        }\n    }\n\n    /// Update cached wrap_row_count without full rebuild.\n    /// Used when no folds are active (identity mapping assumed).\n    pub(super) fn mark_dirty_with_wrap_count(&mut self, wrap_row_count: usize) {\n        self.needs_rebuild = true;\n        self.cached_wrap_row_count = wrap_row_count;\n    }\n\n    /// Get total number of visible display rows\n    pub fn display_row_count(&self) -> usize {\n        if self.folded.is_empty() {\n            return self.cached_wrap_row_count;\n        }\n        self.visible_wrap_rows.len()\n    }\n\n    /// Convert wrap_row to display_row\n    /// Returns None if the wrap_row is hidden by folding\n    pub fn wrap_row_to_display_row(&self, wrap_row: usize) -> Option<usize> {\n        if self.folded.is_empty() {\n            return if wrap_row < self.cached_wrap_row_count {\n                Some(wrap_row)\n            } else {\n                None\n            };\n        }\n        self.wrap_row_to_display_row\n            .get(wrap_row)\n            .copied()\n            .flatten()\n    }\n\n    /// Convert display_row to wrap_row\n    pub fn display_row_to_wrap_row(&self, display_row: usize) -> Option<usize> {\n        if self.folded.is_empty() {\n            return if display_row < self.cached_wrap_row_count {\n                Some(display_row)\n            } else {\n                None\n            };\n        }\n        self.visible_wrap_rows.get(display_row).copied()\n    }\n\n    /// Find the nearest visible display_row for a given wrap_row\n    pub fn nearest_visible_display_row(&self, wrap_row: usize) -> usize {\n        if self.folded.is_empty() {\n            return wrap_row.min(self.cached_wrap_row_count.saturating_sub(1));\n        }\n\n        if let Some(dr) = self.wrap_row_to_display_row(wrap_row) {\n            return dr;\n        }\n\n        match self.visible_wrap_rows.binary_search(&wrap_row) {\n            Ok(idx) => idx,\n            Err(insert_pos) => insert_pos.saturating_sub(1),\n        }\n    }\n\n    /// Set fold candidates (from tree-sitter/LSP), full replacement.\n    pub fn set_candidates(&mut self, mut candidates: Vec<FoldRange>) {\n        // Sort and deduplicate by start_line\n        candidates.sort_by_key(|r| r.start_line);\n        candidates.dedup_by_key(|r| r.start_line);\n        self.candidates = candidates;\n\n        // Remove any folded ranges that are no longer in candidates\n        self.folded.retain(|fold| {\n            self.candidates\n                .iter()\n                .any(|c| c.start_line == fold.start_line)\n        });\n    }\n\n    /// Merge new candidates extracted from an edited region into existing candidates.\n    ///\n    /// Replaces candidates within [edit_start_line, edit_end_line] with `new_candidates`,\n    /// keeping candidates outside the edit range intact.\n    pub fn merge_candidates_for_edit(\n        &mut self,\n        edit_start_line: usize,\n        edit_end_line: usize,\n        new_candidates: Vec<FoldRange>,\n    ) {\n        // Remove old candidates within the edit range (already done by adjust_folds_for_edit)\n        // But do it again in case adjust wasn't called or range differs\n        self.candidates\n            .retain(|c| c.start_line < edit_start_line || c.start_line > edit_end_line);\n\n        // Add new candidates\n        self.candidates.extend(new_candidates);\n        self.candidates.sort_by_key(|r| r.start_line);\n        self.candidates.dedup_by_key(|r| r.start_line);\n    }\n\n    /// Set a fold at the given start_line (must be in candidates)\n    pub fn set_folded(&mut self, start_line: usize, folded: bool) {\n        if folded {\n            // Find the candidate range for this start_line\n            if let Some(candidate) = self.candidates.iter().find(|c| c.start_line == start_line) {\n                // Add to folded if not already present\n                if !self.folded.iter().any(|f| f.start_line == start_line) {\n                    self.folded.push(*candidate);\n                    self.folded.sort_by_key(|r| r.start_line);\n                    self.needs_rebuild = true;\n                }\n            }\n        } else {\n            // Remove from folded\n            self.folded.retain(|f| f.start_line != start_line);\n            self.needs_rebuild = true;\n        }\n    }\n\n    /// Toggle fold at the given start_line\n    pub fn toggle_fold(&mut self, start_line: usize) {\n        let is_folded = self.is_folded_at(start_line);\n        self.set_folded(start_line, !is_folded);\n    }\n\n    /// Check if a line is currently folded\n    pub fn is_folded_at(&self, start_line: usize) -> bool {\n        self.folded.iter().any(|f| f.start_line == start_line)\n    }\n\n    /// Check if a line is a fold candidate\n    pub fn is_fold_candidate(&self, start_line: usize) -> bool {\n        self.candidates.iter().any(|c| c.start_line == start_line)\n    }\n\n    /// Get all fold candidates\n    #[inline]\n    pub fn fold_candidates(&self) -> &[FoldRange] {\n        &self.candidates\n    }\n\n    /// Get all currently folded ranges\n    #[inline]\n    pub fn folded_ranges(&self) -> &[FoldRange] {\n        &self.folded\n    }\n\n    /// Clear all folds\n    #[inline]\n    pub fn clear_folds(&mut self) {\n        self.folded.clear();\n    }\n\n    /// Adjust folds and candidates after a text edit.\n    ///\n    /// - Folds/candidates overlapping the edited line range are removed\n    /// - Folds/candidates after the edit are shifted by line_delta\n    ///\n    /// This avoids expensive full tree traversal on every keystroke.\n    pub fn adjust_folds_for_edit(\n        &mut self,\n        edit_start_line: usize,\n        edit_end_line: usize,\n        line_delta: isize,\n    ) {\n        // Adjust folded ranges\n        if !self.folded.is_empty() {\n            self.folded.retain(|fold| {\n                !(fold.start_line <= edit_end_line && fold.end_line >= edit_start_line)\n            });\n\n            if line_delta != 0 {\n                for fold in &mut self.folded {\n                    if fold.start_line > edit_end_line {\n                        fold.start_line = (fold.start_line as isize + line_delta).max(0) as usize;\n                        fold.end_line = (fold.end_line as isize + line_delta).max(0) as usize;\n                    }\n                }\n            }\n        }\n\n        // Adjust candidates the same way\n        if !self.candidates.is_empty() {\n            self.candidates\n                .retain(|c| !(c.start_line <= edit_end_line && c.end_line >= edit_start_line));\n\n            if line_delta != 0 {\n                for c in &mut self.candidates {\n                    if c.start_line > edit_end_line {\n                        c.start_line = (c.start_line as isize + line_delta).max(0) as usize;\n                        c.end_line = (c.end_line as isize + line_delta).max(0) as usize;\n                    }\n                }\n            }\n        }\n\n        self.needs_rebuild = true;\n    }\n\n    /// Rebuild the fold mapping after wrap_map or fold state changes\n    ///\n    /// This is the core algorithm that projects wrap rows to display rows.\n    pub fn rebuild(&mut self, wrap_map: &WrapMap) {\n        let wrap_row_count = wrap_map.wrap_row_count();\n\n        // Performance optimization: skip rebuild if nothing changed\n        if !self.needs_rebuild && wrap_row_count == self.cached_wrap_row_count {\n            return;\n        }\n\n        self.cached_wrap_row_count = wrap_row_count;\n\n        self.visible_wrap_rows.clear();\n        self.wrap_row_to_display_row = vec![None; wrap_row_count];\n\n        if self.folded.is_empty() {\n            // Fast path: no folds, all wrap rows are visible\n            self.visible_wrap_rows = (0..wrap_row_count).collect();\n            for (display_row, &wrap_row) in self.visible_wrap_rows.iter().enumerate() {\n                self.wrap_row_to_display_row[wrap_row] = Some(display_row);\n            }\n            self.needs_rebuild = false;\n            return;\n        }\n\n        // Build set of hidden wrap_row ranges from folded buffer lines\n        let mut hidden_ranges = Vec::new();\n        for fold in &self.folded {\n            // Hide wrap rows from (start_line + 1) to (end_line - 1) (inclusive)\n            // Both the first line and last line of the fold remain visible\n            let hide_start_line = fold.start_line + 1;\n            let hide_end_line = fold.end_line.saturating_sub(1);\n\n            if hide_start_line > hide_end_line {\n                continue; // No middle lines to hide (0 or 1 lines between start and end)\n            }\n\n            // Get wrap_row ranges for the hidden buffer lines\n            let start_wrap_row = wrap_map.buffer_line_to_first_wrap_row(hide_start_line);\n            let end_wrap_row = if hide_end_line + 1 < wrap_map.buffer_line_count() {\n                wrap_map.buffer_line_to_first_wrap_row(hide_end_line + 1)\n            } else {\n                wrap_row_count\n            };\n\n            if start_wrap_row < end_wrap_row {\n                hidden_ranges.push(start_wrap_row..end_wrap_row);\n            }\n        }\n\n        // Merge overlapping hidden ranges\n        hidden_ranges.sort_by_key(|r| r.start);\n        let mut merged_hidden = Vec::new();\n        for range in hidden_ranges {\n            if let Some(last) = merged_hidden.last_mut() {\n                if range.start <= *last {\n                    // Overlapping or adjacent, merge\n                    *last = (*last).max(range.end);\n                } else {\n                    merged_hidden.push(range.start);\n                    merged_hidden.push(range.end);\n                }\n            } else {\n                merged_hidden.push(range.start);\n                merged_hidden.push(range.end);\n            }\n        }\n\n        // Scan all wrap rows and filter out hidden ones\n        let mut display_row = 0;\n        let mut hidden_iter = merged_hidden.chunks_exact(2);\n        let mut current_hidden = hidden_iter.next();\n\n        for wrap_row in 0..wrap_row_count {\n            // Check if wrap_row is in current hidden range\n            let is_hidden = if let Some(&[start, end]) = current_hidden {\n                if wrap_row >= end {\n                    current_hidden = hidden_iter.next();\n                    if let Some(&[new_start, new_end]) = current_hidden {\n                        wrap_row >= new_start && wrap_row < new_end\n                    } else {\n                        false\n                    }\n                } else {\n                    wrap_row >= start && wrap_row < end\n                }\n            } else {\n                false\n            };\n\n            if !is_hidden {\n                self.visible_wrap_rows.push(wrap_row);\n                self.wrap_row_to_display_row[wrap_row] = Some(display_row);\n                display_row += 1;\n            }\n        }\n\n        self.needs_rebuild = false;\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/display_map/folding.rs",
    "content": "use std::ops::Range;\n\n#[cfg(not(target_family = \"wasm\"))]\nuse tree_sitter::Node;\n#[cfg(not(target_family = \"wasm\"))]\npub use tree_sitter::Tree;\n\n#[cfg(target_family = \"wasm\")]\n/// Stub type for tree-sitter Tree on WASM (tree-sitter not available).\npub struct Tree;\n\n#[cfg(not(target_family = \"wasm\"))]\n/// Minimum line span for a node to be considered foldable.\nconst MIN_FOLD_LINES: usize = 2;\n\n/// A fold range representing a foldable code region.\n///\n/// The fold range spans from start_line to end_line (inclusive).\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct FoldRange {\n    /// Start line (inclusive)\n    pub start_line: usize,\n    /// End line (inclusive)\n    pub end_line: usize,\n}\n\nimpl FoldRange {\n    pub fn new(start_line: usize, end_line: usize) -> Self {\n        assert!(\n            start_line <= end_line,\n            \"fold start_line must be <= end_line\"\n        );\n        Self {\n            start_line,\n            end_line,\n        }\n    }\n}\n\n// ==================== Native Implementation (with tree-sitter) ====================\n\n#[cfg(not(target_family = \"wasm\"))]\n/// Check if a named node qualifies as a fold candidate.\n///\n/// Uses a structural heuristic: any **named** node spanning ≥ MIN_FOLD_LINES\n/// is foldable. tree-sitter already parses code into semantic units (functions,\n/// classes, blocks, etc.), so named nodes naturally correspond to meaningful\n/// foldable regions across all languages without a per-language node-type list.\nfn is_foldable_node(node: &Node) -> bool {\n    // Skip root node (e.g. `source_file`) and unnamed tokens\n    if !node.is_named() || node.parent().is_none() {\n        return false;\n    }\n\n    let start = node.start_position().row;\n    let end = node.end_position().row;\n    end.saturating_sub(start) >= MIN_FOLD_LINES\n}\n\n#[cfg(not(target_family = \"wasm\"))]\n/// Extract fold ranges from a tree-sitter syntax tree (full traversal).\npub fn extract_fold_ranges(tree: &Tree) -> Vec<FoldRange> {\n    let mut ranges = Vec::new();\n    collect_foldable_nodes(tree.root_node(), &mut ranges);\n\n    ranges.sort_by_key(|r| r.start_line);\n    ranges.dedup_by_key(|r| r.start_line);\n    ranges\n}\n\n#[cfg(not(target_family = \"wasm\"))]\n/// Extract fold ranges only within a byte range (for incremental updates after edits).\n///\n/// Skips subtrees entirely outside the range, making it O(nodes in range)\n/// instead of O(all nodes in tree).\npub fn extract_fold_ranges_in_range(tree: &Tree, byte_range: Range<usize>) -> Vec<FoldRange> {\n    let mut ranges = Vec::new();\n    collect_foldable_nodes_in_range(tree.root_node(), &byte_range, &mut ranges);\n\n    ranges.sort_by_key(|r| r.start_line);\n    ranges.dedup_by_key(|r| r.start_line);\n    ranges\n}\n\n#[cfg(not(target_family = \"wasm\"))]\n/// Recursively collect foldable nodes, skipping subtrees outside byte_range.\nfn collect_foldable_nodes_in_range(\n    node: Node,\n    byte_range: &Range<usize>,\n    ranges: &mut Vec<FoldRange>,\n) {\n    if node.end_byte() <= byte_range.start || node.start_byte() >= byte_range.end {\n        return;\n    }\n\n    if is_foldable_node(&node) {\n        ranges.push(FoldRange {\n            start_line: node.start_position().row,\n            end_line: node.end_position().row,\n        });\n    }\n\n    let mut cursor = node.walk();\n    for child in node.children(&mut cursor) {\n        collect_foldable_nodes_in_range(child, byte_range, ranges);\n    }\n}\n\n#[cfg(not(target_family = \"wasm\"))]\n/// Recursively collect foldable nodes from the syntax tree (full traversal).\nfn collect_foldable_nodes(node: Node, ranges: &mut Vec<FoldRange>) {\n    if is_foldable_node(&node) {\n        ranges.push(FoldRange {\n            start_line: node.start_position().row,\n            end_line: node.end_position().row,\n        });\n    }\n\n    let mut cursor = node.walk();\n    for child in node.children(&mut cursor) {\n        collect_foldable_nodes(child, ranges);\n    }\n}\n\n// ==================== WASM Stub Implementation ====================\n\n#[cfg(target_family = \"wasm\")]\n/// Extract fold ranges - WASM stub (returns empty, no tree-sitter).\npub fn extract_fold_ranges(_tree: &Tree) -> Vec<FoldRange> {\n    Vec::new()\n}\n\n#[cfg(target_family = \"wasm\")]\n/// Extract fold ranges in range - WASM stub (returns empty, no tree-sitter).\npub fn extract_fold_ranges_in_range(_tree: &Tree, _byte_range: Range<usize>) -> Vec<FoldRange> {\n    Vec::new()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_fold_range_ordering() {\n        let mut ranges = vec![\n            FoldRange {\n                start_line: 10,\n                end_line: 20,\n            },\n            FoldRange {\n                start_line: 5,\n                end_line: 15,\n            },\n            FoldRange {\n                start_line: 5,\n                end_line: 15,\n            },\n            FoldRange {\n                start_line: 1,\n                end_line: 30,\n            },\n        ];\n\n        ranges.sort_by_key(|r| r.start_line);\n        ranges.dedup_by_key(|r| r.start_line);\n\n        assert_eq!(ranges.len(), 3);\n        assert_eq!(ranges[0].start_line, 1);\n        assert_eq!(ranges[1].start_line, 5);\n        assert_eq!(ranges[2].start_line, 10);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/display_map/mod.rs",
    "content": "/// Display mapping system for Editor/Input.\n///\n/// This module implements a layered display mapping architecture:\n/// - **WrapMap**: Handles soft-wrapping (buffer → wrap rows)\n/// - **FoldMap**: Handles folding (wrap rows → display rows)\n/// - **DisplayMap**: Public facade for Editor/Input\n///\n/// The goal is to provide a clean, unified API where Editor only needs to know\n/// about `BufferPoint ↔ DisplayPoint` mapping, without worrying about internal wrap/fold complexity.\nmod display_map;\nmod fold_map;\n#[cfg(not(target_family = \"wasm\"))]\nmod folding;\n#[cfg(target_family = \"wasm\")]\npub mod folding;\nmod text_wrapper;\nmod wrap_map;\n\n// Re-export public API\npub use self::display_map::DisplayMap;\npub(crate) use self::text_wrapper::LineLayout;\n\n// Re-export FoldRange and extract_fold_ranges\npub use folding::{FoldRange, extract_fold_ranges};\n\n/// Position in the buffer (logical text).\n///\n/// - `line`: 0-based logical line number (split by `\\n`)\n/// - `col`: 0-based column offset (byte offset)\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct BufferPoint {\n    pub line: usize,\n    pub col: usize,\n}\n\nimpl BufferPoint {\n    pub fn new(line: usize, col: usize) -> Self {\n        Self { line, col }\n    }\n}\n\n/// Position after soft-wrapping but before folding (internal).\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub(super) struct WrapPoint {\n    pub row: usize,\n    pub col: usize,\n}\n\nimpl WrapPoint {\n    pub fn new(row: usize, col: usize) -> Self {\n        Self { row, col }\n    }\n}\n\n/// Final display position (after soft-wrapping and folding).\n///\n/// - `row`: 0-based display row (final visible row)\n/// - `col`: 0-based display column\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct DisplayPoint {\n    pub row: usize,\n    pub col: usize,\n}\n\nimpl DisplayPoint {\n    pub fn new(row: usize, col: usize) -> Self {\n        Self { row, col }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/display_map/text_wrapper.rs",
    "content": "use std::ops::Range;\n\nuse gpui::{\n    App, Font, Half, LineFragment, Pixels, Point, ShapedLine, Size, TextAlign, Window, point, px,\n    size,\n};\nuse ropey::Rope;\nuse smallvec::SmallVec;\n\nuse crate::input::{\n    LastLayout, Point as TreeSitterPoint, RopeExt, WhitespaceIndicators,\n};\n\n/// A line with soft wrapped lines info.\n#[derive(Debug, Clone)]\npub(crate) struct LineItem {\n    /// The original line text, without end `\\n`.\n    line: Rope,\n    /// The soft wrapped lines relative byte range (0..line.len) of this line (Include first line).\n    ///\n    /// Not contains the line end `\\n`.\n    pub(crate) wrapped_lines: Vec<Range<usize>>,\n}\n\nimpl LineItem {\n    /// Get the bytes length of this line.\n    #[inline]\n    pub(crate) fn len(&self) -> usize {\n        self.line.len()\n    }\n\n    /// Get number of soft wrapped lines of this line (include the first line).\n    #[inline]\n    pub(crate) fn lines_len(&self) -> usize {\n        self.wrapped_lines.len()\n    }\n\n}\n\n#[derive(Debug, Default)]\npub(crate) struct LongestRow {\n    /// The 0-based row index.\n    pub row: usize,\n    /// The bytes length of the longest line.\n    pub len: usize,\n}\n\n/// Used to prepare the text with soft wrap to be get lines to displayed in the Editor.\n///\n/// After use lines to calculate the scroll size of the Editor.\npub(crate) struct TextWrapper {\n    text: Rope,\n    /// Total wrapped lines (Inlucde the first line), value is start and end index of the line.\n    soft_lines: usize,\n    font: Font,\n    font_size: Pixels,\n    /// If is none, it means the text is not wrapped\n    wrap_width: Option<Pixels>,\n    /// The longest (row, bytes len) in characters, used to calculate the horizontal scroll width.\n    pub(crate) longest_row: LongestRow,\n    /// The lines by split \\n\n    pub(crate) lines: Vec<LineItem>,\n\n    _initialized: bool,\n}\n\n#[allow(unused)]\nimpl TextWrapper {\n    pub(crate) fn new(font: Font, font_size: Pixels, wrap_width: Option<Pixels>) -> Self {\n        Self {\n            text: Rope::new(),\n            font,\n            font_size,\n            wrap_width,\n            soft_lines: 0,\n            longest_row: LongestRow::default(),\n            lines: Vec::new(),\n            _initialized: false,\n        }\n    }\n\n    #[inline]\n    pub(crate) fn set_default_text(&mut self, text: &Rope) {\n        self.text = text.clone();\n    }\n\n    /// Get reference to the rope text.\n    #[inline]\n    pub(crate) fn text(&self) -> &Rope {\n        &self.text\n    }\n\n    /// Get the total number of lines including wrapped lines.\n    #[inline]\n    pub(crate) fn len(&self) -> usize {\n        self.soft_lines\n    }\n\n    /// Get the line item by row index.\n    #[inline]\n    pub(crate) fn line(&self, row: usize) -> Option<&LineItem> {\n        self.lines.iter().skip(row).next()\n    }\n\n    pub(crate) fn set_wrap_width(&mut self, wrap_width: Option<Pixels>, cx: &mut App) {\n        if wrap_width == self.wrap_width {\n            return;\n        }\n\n        self.wrap_width = wrap_width;\n        self.update_all(&self.text.clone(), cx);\n    }\n\n    pub(crate) fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) {\n        if self.font.eq(&font) && self.font_size == font_size {\n            return;\n        }\n\n        self.font = font;\n        self.font_size = font_size;\n        self.update_all(&self.text.clone(), cx);\n    }\n\n    pub(crate) fn prepare_if_need(&mut self, text: &Rope, cx: &mut App) -> bool {\n        if self._initialized {\n            return false;\n        }\n        self._initialized = true;\n        self.update_all(text, cx);\n        true\n    }\n\n    /// Update the text wrapper and recalculate the wrapped lines.\n    ///\n    /// If the `text` is the same as the current text, do nothing.\n    ///\n    /// - `changed_text`: The text [`Rope`] that has changed.\n    /// - `range`: The `selected_range` before change.\n    /// - `new_text`: The inserted text.\n    /// - `force`: Whether to force the update, if false, the update will be skipped if the text is the same.\n    /// - `cx`: The application context.\n    pub(crate) fn update(\n        &mut self,\n        changed_text: &Rope,\n        range: &Range<usize>,\n        new_text: &Rope,\n        cx: &mut App,\n    ) {\n        let mut line_wrapper = cx\n            .text_system()\n            .line_wrapper(self.font.clone(), self.font_size);\n        self._update(\n            changed_text,\n            range,\n            new_text,\n            &mut |line_str, wrap_width| {\n                line_wrapper\n                    .wrap_line(&[LineFragment::text(line_str)], wrap_width)\n                    .collect()\n            },\n        );\n    }\n\n    fn _update<F>(\n        &mut self,\n        changed_text: &Rope,\n        range: &Range<usize>,\n        new_text: &Rope,\n        wrap_line: &mut F,\n    ) where\n        F: FnMut(&str, Pixels) -> Vec<gpui::Boundary>,\n    {\n        // Remove the old changed lines.\n        let start_row = self.text.offset_to_point(range.start).row;\n        let start_row = start_row.min(self.lines.len().saturating_sub(1));\n        let end_row = self.text.offset_to_point(range.end).row;\n        let end_row = end_row.min(self.lines.len().saturating_sub(1));\n        let rows_range = start_row..=end_row;\n\n        if rows_range.contains(&self.longest_row.row) {\n            self.longest_row = LongestRow::default();\n        }\n\n        let mut longest_row_ix = self.longest_row.row;\n        let mut longest_row_len = self.longest_row.len;\n\n        // To add the new lines.\n        let new_start_row = changed_text.offset_to_point(range.start).row;\n        let new_start_offset = changed_text.line_start_offset(new_start_row);\n        let new_end_row = changed_text\n            .offset_to_point(range.start + new_text.len())\n            .row;\n        let new_end_offset = changed_text.line_end_offset(new_end_row);\n        let new_range = new_start_offset..new_end_offset;\n\n        let mut new_lines = vec![];\n        let wrap_width = self.wrap_width;\n\n        // line not contains `\\n`.\n        for (ix, line) in Rope::from(changed_text.slice(new_range))\n            .iter_lines()\n            .enumerate()\n        {\n            let line_str = line.to_string();\n            let mut wrapped_lines = vec![];\n            let mut prev_boundary_ix = 0;\n\n            if line_str.len() > longest_row_len {\n                longest_row_ix = new_start_row + ix;\n                longest_row_len = line_str.len();\n            }\n\n            // If wrap_width is Pixels::MAX, skip wrapping to disable word wrap\n            if let Some(wrap_width) = wrap_width {\n                // Here only have wrapped line, if there is no wrap meet, the `line_wraps` result will empty.\n                for boundary in wrap_line(&line_str, wrap_width) {\n                    wrapped_lines.push(prev_boundary_ix..boundary.ix);\n                    prev_boundary_ix = boundary.ix;\n                }\n            }\n\n            // Reset of the line\n            if !line_str[prev_boundary_ix..].is_empty() || prev_boundary_ix == 0 {\n                wrapped_lines.push(prev_boundary_ix..line.len());\n            }\n\n            new_lines.push(LineItem {\n                line: Rope::from(line),\n                wrapped_lines,\n            });\n        }\n\n        if self.lines.len() == 0 {\n            self.lines = new_lines;\n        } else {\n            self.lines.splice(rows_range, new_lines);\n        }\n\n        self.text = changed_text.clone();\n        self.soft_lines = self.lines.iter().map(|l| l.lines_len()).sum();\n        self.longest_row = LongestRow {\n            row: longest_row_ix,\n            len: longest_row_len,\n        }\n    }\n\n    /// Update the text wrapper and recalculate the wrapped lines.\n    ///\n    /// If the `text` is the same as the current text, do nothing.\n    fn update_all(&mut self, text: &Rope, cx: &mut App) {\n        self.update(text, &(0..text.len()), &text, cx);\n    }\n\n    /// Return display point (with soft wrap) from the given byte offset in the text.\n    ///\n    /// Panics if the `offset` is out of bounds.\n    pub(crate) fn offset_to_display_point(&self, offset: usize) -> WrapDisplayPoint {\n        let row = self.text.offset_to_point(offset).row;\n        let start = self.text.line_start_offset(row);\n        let line = &self.lines[row];\n\n        let mut wrapped_row = self\n            .lines\n            .iter()\n            .take(row)\n            .map(|l| l.lines_len())\n            .sum::<usize>();\n\n        let local_offset = offset.saturating_sub(start);\n        for (ix, range) in line.wrapped_lines.iter().enumerate() {\n            if range.contains(&local_offset) {\n                return WrapDisplayPoint::new(\n                    wrapped_row + ix,\n                    ix,\n                    local_offset.saturating_sub(range.start),\n                );\n            }\n        }\n\n        // Otherwise return the eof of the line.\n        let last_range = line.wrapped_lines.last().unwrap_or(&(0..0));\n        let ix = line.lines_len().saturating_sub(1);\n        return WrapDisplayPoint::new(wrapped_row + ix, ix, last_range.len());\n    }\n\n    /// Return byte offset in the text from the given display point (with soft wrap).\n    ///\n    /// Panics if the `point.row` is out of bounds.\n    pub(crate) fn display_point_to_offset(&self, point: WrapDisplayPoint) -> usize {\n        let mut wrapped_row = 0;\n        for (row, line) in self.lines.iter().enumerate() {\n            if wrapped_row + line.lines_len() > point.row {\n                let line_start = self.text.line_start_offset(row);\n                let local_row = point.row.saturating_sub(wrapped_row);\n                if let Some(range) = line.wrapped_lines.get(local_row) {\n                    return line_start + (range.start + point.column).min(range.end);\n                } else {\n                    // If not found, return the end of the line.\n                    return line_start + line.len();\n                }\n            }\n\n            wrapped_row += line.lines_len();\n        }\n\n        return self.text.len();\n    }\n\n    pub(crate) fn display_point_to_point(&self, point: WrapDisplayPoint) -> TreeSitterPoint {\n        let offset = self.display_point_to_offset(point);\n        self.text.offset_to_point(offset)\n    }\n\n    pub(crate) fn point_to_display_point(&self, point: TreeSitterPoint) -> WrapDisplayPoint {\n        let offset = self.text.point_to_offset(point);\n        self.offset_to_display_point(offset)\n    }\n}\n\n/// A display point within the soft-wrapped text.\n///\n/// This represents a position in the text after soft-wrapping,\n/// with an additional `local_row` field tracking the wrap line\n/// within the original buffer line.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub(crate) struct WrapDisplayPoint {\n    /// The 0-based soft wrapped row index in the text.\n    pub row: usize,\n    /// The 0-based row index in local line (include first line).\n    ///\n    /// This value only valid when return from [`TextWrapper::offset_to_display_point`], otherwise it will be ignored.\n    pub local_row: usize,\n    /// The 0-based column byte index in the display line (with soft wrap).\n    pub column: usize,\n}\n\nimpl WrapDisplayPoint {\n    pub fn new(row: usize, local_row: usize, column: usize) -> Self {\n        Self {\n            row,\n            local_row,\n            column,\n        }\n    }\n}\n\n/// The layout info of a line with soft wrapped lines.\npub(crate) struct LineLayout {\n    /// Total bytes length of this line.\n    len: usize,\n    /// The soft wrapped lines of this line (Include the first line).\n    pub(crate) wrapped_lines: SmallVec<[ShapedLine; 1]>,\n    pub(crate) longest_width: Pixels,\n    pub(crate) whitespace_indicators: Option<WhitespaceIndicators>,\n    /// Whitespace indicators: (line_index, x_position, is_tab)\n    pub(crate) whitespace_chars: Vec<(usize, Pixels, bool)>,\n}\n\nimpl LineLayout {\n    pub(crate) fn new() -> Self {\n        Self {\n            len: 0,\n            longest_width: px(0.),\n            wrapped_lines: SmallVec::new(),\n            whitespace_chars: Vec::new(),\n            whitespace_indicators: None,\n        }\n    }\n\n    pub(crate) fn lines(mut self, wrapped_lines: SmallVec<[ShapedLine; 1]>) -> Self {\n        self.set_wrapped_lines(wrapped_lines);\n        self\n    }\n\n    pub(crate) fn set_wrapped_lines(&mut self, wrapped_lines: SmallVec<[ShapedLine; 1]>) {\n        self.len = wrapped_lines.iter().map(|l| l.len).sum();\n        let width = wrapped_lines\n            .iter()\n            .map(|l| l.width)\n            .max()\n            .unwrap_or_default();\n        self.longest_width = width;\n        self.wrapped_lines = wrapped_lines;\n    }\n\n    pub(crate) fn with_whitespaces(mut self, indicators: Option<WhitespaceIndicators>) -> Self {\n        self.whitespace_indicators = indicators;\n        let Some(indicators) = self.whitespace_indicators.as_ref() else {\n            return self;\n        };\n\n        let space_indicator_offset = indicators.space.width.half();\n\n        for (line_index, wrapped_line) in self.wrapped_lines.iter().enumerate() {\n            for (relative_offset, c) in wrapped_line.text.char_indices() {\n                if matches!(c, ' ' | '\\t') {\n                    let is_tab = c == '\\t';\n                    let start_x = wrapped_line.x_for_index(relative_offset);\n                    let end_x = wrapped_line.x_for_index(relative_offset + c.len_utf8());\n                    // Center the indicator in the actual character's space\n                    let x_position = if c == ' ' {\n                        (start_x + end_x).half() - space_indicator_offset\n                    } else {\n                        start_x\n                    };\n\n                    self.whitespace_chars.push((line_index, x_position, is_tab));\n                }\n            }\n        }\n        self\n    }\n\n    #[inline]\n    pub(crate) fn len(&self) -> usize {\n        self.len\n    }\n\n    /// Get the position (x, y) for the given index in this line layout.\n    ///\n    /// - The `offset` is a local byte index in this line layout.\n    /// - The return value is relative to the top-left corner of this line layout, start from (0, 0)\n    pub(crate) fn position_for_index(\n        &self,\n        offset: usize,\n        last_layout: &LastLayout,\n    ) -> Option<Point<Pixels>> {\n        let mut acc_len = 0;\n        let mut offset_y = px(0.);\n\n        let x_offset = last_layout.alignment_offset(self.longest_width);\n\n        for (i, line) in self.wrapped_lines.iter().enumerate() {\n            let is_last = i + 1 == self.wrapped_lines.len();\n            let line_len = if is_last { line.len + 1 } else { line.len };\n\n            let range = acc_len..(acc_len + line_len);\n            if range.contains(&offset) {\n                let x = line.x_for_index(offset.saturating_sub(acc_len)) + x_offset;\n                return Some(point(x, offset_y));\n            }\n            acc_len += line_len;\n            offset_y += last_layout.line_height;\n        }\n\n        None\n    }\n\n    /// Get the closest index for the given x in this line layout.\n    pub(crate) fn closest_index_for_x(&self, x: Pixels, last_layout: &LastLayout) -> usize {\n        let mut acc_len = 0;\n        let x_offset = last_layout.alignment_offset(self.longest_width);\n        let x = x - x_offset;\n\n        for (i, line) in self.wrapped_lines.iter().enumerate() {\n            let is_last = i + 1 == self.wrapped_lines.len();\n            if x <= line.width {\n                let mut ix = line.closest_index_for_x(x);\n                if !is_last && ix == line.text.len() {\n                    // For soft wrap line, we can't put the cursor at the end of the line.\n                    let c_len = line.text.chars().last().map(|c| c.len_utf8()).unwrap_or(0);\n                    ix = ix.saturating_sub(c_len);\n                }\n\n                return acc_len + ix;\n            }\n            acc_len += line.text.len();\n        }\n\n        acc_len\n    }\n\n    /// Get the index for the given position (x, y) in this line layout.\n    ///\n    /// The `pos` is relative to the top-left corner of this line layout, start from (0, 0)\n    /// The return value is a local byte index in this line layout, start from 0.\n    pub(crate) fn closest_index_for_position(\n        &self,\n        pos: Point<Pixels>,\n        last_layout: &LastLayout,\n    ) -> Option<usize> {\n        let mut offset = 0;\n        let mut line_top = px(0.);\n        let x_offset = last_layout.alignment_offset(self.longest_width);\n        for (i, line) in self.wrapped_lines.iter().enumerate() {\n            let is_last = i + 1 == self.wrapped_lines.len();\n            let line_bottom = line_top + last_layout.line_height;\n            if pos.y >= line_top && pos.y < line_bottom {\n                let mut ix = line.closest_index_for_x(pos.x - x_offset);\n                if !is_last && ix == line.text.len() {\n                    // For soft wrap line, we can't put the cursor at the end of the line.\n                    let c_len = line.text.chars().last().map(|c| c.len_utf8()).unwrap_or(0);\n                    ix = ix.saturating_sub(c_len);\n                }\n                return Some(offset + ix);\n            }\n\n            offset += line.text.len();\n            line_top = line_bottom;\n        }\n\n        None\n    }\n\n    pub(crate) fn index_for_position(\n        &self,\n        pos: Point<Pixels>,\n        last_layout: &LastLayout,\n    ) -> Option<usize> {\n        let mut offset = 0;\n        let mut line_top = px(0.);\n        let x_offset = last_layout.alignment_offset(self.longest_width);\n        for line in self.wrapped_lines.iter() {\n            let line_bottom = line_top + last_layout.line_height;\n            if pos.y >= line_top && pos.y < line_bottom {\n                let ix = line.index_for_x(pos.x - x_offset)?;\n                return Some(offset + ix);\n            }\n\n            offset += line.text.len();\n            line_top = line_bottom;\n        }\n\n        None\n    }\n\n    pub(crate) fn size(&self, line_height: Pixels) -> Size<Pixels> {\n        size(self.longest_width, self.wrapped_lines.len() * line_height)\n    }\n\n    pub(crate) fn paint(\n        &self,\n        pos: Point<Pixels>,\n        line_height: Pixels,\n        text_align: TextAlign,\n        align_width: Option<Pixels>,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        for (ix, line) in self.wrapped_lines.iter().enumerate() {\n            _ = line.paint(\n                pos + point(px(0.), ix * line_height),\n                line_height,\n                text_align,\n                align_width,\n                window,\n                cx,\n            );\n        }\n\n        // Paint whitespace indicators\n        if let Some(indicators) = self.whitespace_indicators.as_ref() {\n            for (line_index, x_position, is_tab) in &self.whitespace_chars {\n                let invisible = if *is_tab {\n                    indicators.tab.clone()\n                } else {\n                    indicators.space.clone()\n                };\n\n                let origin = point(\n                    pos.x + *x_position,\n                    pos.y + *line_index as f32 * line_height,\n                );\n\n                _ = invisible.paint(origin, line_height, text_align, align_width, window, cx);\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use gpui::{Boundary, FontFeatures, FontStyle, FontWeight, px};\n\n    #[test]\n    fn test_update() {\n        let font = gpui::Font {\n            family: \"Arial\".into(),\n            weight: FontWeight::default(),\n            style: FontStyle::Normal,\n            features: FontFeatures::default(),\n            fallbacks: None,\n        };\n\n        let mut wrapper = TextWrapper::new(font, px(14.), None);\n        let mut text = Rope::from(\n            \"Hello, 世界!\\r\\nThis is second line.\\nThis is third line.\\n这里是第 4 行。\",\n        );\n\n        fn fake_wrap_line(_line: &str, _wrap_width: Pixels) -> Vec<Boundary> {\n            vec![]\n        }\n\n        #[track_caller]\n        fn assert_wrapper_lines(text: &Rope, wrapper: &TextWrapper, expected_lines: &[&[&str]]) {\n            let mut actual_lines = vec![];\n            let mut offset = 0;\n            for line in wrapper.lines.iter() {\n                actual_lines.push(\n                    line.wrapped_lines\n                        .iter()\n                        .map(|range| text.slice(offset + range.start..offset + range.end))\n                        .collect::<Vec<_>>(),\n                );\n                // +1 \\n\n                offset += line.len() + 1;\n            }\n            assert_eq!(actual_lines, expected_lines);\n        }\n\n        wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);\n        assert_eq!(wrapper.lines.len(), 4);\n        assert_wrapper_lines(\n            &text,\n            &wrapper,\n            &[\n                &[\"Hello, 世界!\\r\"],\n                &[\"This is second line.\"],\n                &[\"This is third line.\"],\n                &[\"这里是第 4 行。\"],\n            ],\n        );\n\n        // Add a new text to end\n        let range = text.len()..text.len();\n        let new_text = \"New text\";\n        text.replace(range.clone(), new_text);\n        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);\n        assert_eq!(\n            text.to_string(),\n            \"Hello, 世界!\\r\\nThis is second line.\\nThis is third line.\\n这里是第 4 行。New text\"\n        );\n        assert_eq!(wrapper.lines.len(), 4);\n        assert_eq!(wrapper.lines.len(), 4);\n        assert_wrapper_lines(\n            &text,\n            &wrapper,\n            &[\n                &[\"Hello, 世界!\\r\"],\n                &[\"This is second line.\"],\n                &[\"This is third line.\"],\n                &[\"这里是第 4 行。New text\"],\n            ],\n        );\n\n        // Replace first line `Hello` to `AAA`\n        let range = 0..5;\n        let new_text = \"AAA\";\n        text.replace(range.clone(), new_text);\n        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);\n        assert_eq!(\n            text.to_string(),\n            \"AAA, 世界!\\r\\nThis is second line.\\nThis is third line.\\n这里是第 4 行。New text\"\n        );\n        assert_eq!(wrapper.lines.len(), 4);\n        assert_wrapper_lines(\n            &text,\n            &wrapper,\n            &[\n                &[\"AAA, 世界!\\r\"],\n                &[\"This is second line.\"],\n                &[\"This is third line.\"],\n                &[\"这里是第 4 行。New text\"],\n            ],\n        );\n\n        // Remove the second line\n        let start_offset = text.line_start_offset(1);\n        let end_offset = text.line_end_offset(1);\n        let range = start_offset..end_offset + 1;\n        text.replace(range.clone(), \"\");\n        wrapper._update(&text, &range, &Rope::from(\"\"), &mut fake_wrap_line);\n        assert_eq!(\n            text.to_string(),\n            \"AAA, 世界!\\r\\nThis is third line.\\n这里是第 4 行。New text\"\n        );\n        assert_eq!(wrapper.lines.len(), 3);\n        assert_wrapper_lines(\n            &text,\n            &wrapper,\n            &[\n                &[\"AAA, 世界!\\r\"],\n                &[\"This is third line.\"],\n                &[\"这里是第 4 行。New text\"],\n            ],\n        );\n\n        // Replace the first 2 lines to \"This is a new line.\"\n        let range = text.line_start_offset(0)..text.line_end_offset(1) + 1;\n        let new_text = \"This is a new line.\\nThis is new line 2.\\n\";\n        text.replace(range.clone(), new_text);\n        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);\n        assert_eq!(\n            text.to_string(),\n            \"This is a new line.\\nThis is new line 2.\\n这里是第 4 行。New text\"\n        );\n        assert_eq!(wrapper.lines.len(), 3);\n        assert_wrapper_lines(\n            &text,\n            &wrapper,\n            &[\n                &[\"This is a new line.\"],\n                &[\"This is new line 2.\"],\n                &[\"这里是第 4 行。New text\"],\n            ],\n        );\n\n        // Add a new line at the end\n        let range = text.len()..text.len();\n        let new_text = \"\\nThis is a new line at the end.\";\n        text.replace(range.clone(), new_text);\n        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);\n        assert_eq!(\n            text.to_string(),\n            \"This is a new line.\\nThis is new line 2.\\n这里是第 4 行。New text\\nThis is a new line at the end.\"\n        );\n        assert_eq!(wrapper.lines.len(), 4);\n        assert_wrapper_lines(\n            &text,\n            &wrapper,\n            &[\n                &[\"This is a new line.\"],\n                &[\"This is new line 2.\"],\n                &[\"这里是第 4 行。New text\"],\n                &[\"This is a new line at the end.\"],\n            ],\n        );\n\n        // Add a new line at the beginning\n        let range = 0..0;\n        let new_text = \"This is a new line at the beginning.\\n\";\n        text.replace(range.clone(), new_text);\n        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);\n        assert_eq!(\n            text.to_string(),\n            \"This is a new line at the beginning.\\nThis is a new line.\\nThis is new line 2.\\n这里是第 4 行。New text\\nThis is a new line at the end.\"\n        );\n        assert_eq!(wrapper.lines.len(), 5);\n        assert_wrapper_lines(\n            &text,\n            &wrapper,\n            &[\n                &[\"This is a new line at the beginning.\"],\n                &[\"This is a new line.\"],\n                &[\"This is new line 2.\"],\n                &[\"这里是第 4 行。New text\"],\n                &[\"This is a new line at the end.\"],\n            ],\n        );\n\n        // Remove all to at least one line in `lines`.\n        let range = 0..text.len();\n        let new_text = \"\";\n        text.replace(range.clone(), new_text);\n        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);\n        assert_eq!(text.to_string(), \"\");\n        assert_eq!(wrapper.lines.len(), 1);\n        assert_eq!(wrapper.lines[0].wrapped_lines, vec![0..0]);\n\n        // Test update_all\n        let range = 0..text.len();\n        let new_text = \"This is a full text.\\nThis is a second line.\";\n        text.replace(range.clone(), new_text);\n        wrapper._update(&text, &range, &text, &mut fake_wrap_line);\n        assert_eq!(\n            text.to_string(),\n            \"This is a full text.\\nThis is a second line.\"\n        );\n        assert_eq!(wrapper.lines.len(), 2);\n    }\n\n    #[test]\n    fn test_line_layout() {\n        let mut line_layout = LineLayout::new();\n\n        let line1 = ShapedLine::default().with_len(100);\n        let line2 = ShapedLine::default().with_len(50);\n        let wrapped_lines = smallvec::smallvec![line1, line2];\n        line_layout.set_wrapped_lines(wrapped_lines);\n        assert_eq!(line_layout.len(), 150);\n        assert_eq!(line_layout.wrapped_lines.len(), 2);\n    }\n\n    #[test]\n    fn test_offset_to_display_point() {\n        let font = gpui::Font {\n            family: \"Arial\".into(),\n            weight: FontWeight::default(),\n            style: FontStyle::Normal,\n            features: FontFeatures::default(),\n            fallbacks: None,\n        };\n\n        let mut wrapper = TextWrapper::new(font, px(14.), None);\n        wrapper.text = Rope::from(\n            \"Hello, 世界!\\r\\nThis is second line.\\nThis is third line.\\n这里是第 4 行。\",\n        );\n        wrapper.lines = vec![\n            // range: 0..15\n            LineItem {\n                line: Rope::from(\"Hello, 世界!\\r\"),\n                wrapped_lines: vec![0..15],\n            },\n            // range: 16..36\n            LineItem {\n                line: Rope::from(\"This is second line.\"),\n                wrapped_lines: vec![0..10, 10..20],\n            },\n            // range: 37..56\n            LineItem {\n                line: Rope::from(\"This is third line.\"),\n                wrapped_lines: vec![0..9, 9..15, 15..20],\n            },\n            // range: 57..79\n            LineItem {\n                line: Rope::from(\"这里是第 4 行。\"),\n                wrapped_lines: vec![0..22],\n            },\n        ];\n\n        assert_eq!(\n            wrapper.offset_to_display_point(12),\n            WrapDisplayPoint::new(0, 0, 12)\n        );\n        assert_eq!(\n            wrapper.offset_to_display_point(15),\n            WrapDisplayPoint::new(0, 0, 15)\n        );\n\n        assert_eq!(\n            wrapper.offset_to_display_point(16),\n            WrapDisplayPoint::new(1, 0, 0)\n        );\n        assert_eq!(\n            wrapper.offset_to_display_point(21),\n            WrapDisplayPoint::new(1, 0, 5)\n        );\n        assert_eq!(\n            wrapper.offset_to_display_point(27),\n            WrapDisplayPoint::new(2, 1, 1)\n        );\n        assert_eq!(\n            wrapper.offset_to_display_point(37),\n            WrapDisplayPoint::new(3, 0, 0)\n        );\n        assert_eq!(\n            wrapper.offset_to_display_point(54),\n            WrapDisplayPoint::new(5, 2, 2)\n        );\n        assert_eq!(\n            wrapper.offset_to_display_point(59),\n            WrapDisplayPoint::new(6, 0, 2)\n        );\n\n        assert_eq!(\n            wrapper.display_point_to_offset(WrapDisplayPoint::new(6, 0, 2)),\n            59\n        );\n        assert_eq!(\n            wrapper.display_point_to_offset(WrapDisplayPoint::new(5, 2, 2)),\n            54\n        );\n        assert_eq!(\n            wrapper.display_point_to_offset(WrapDisplayPoint::new(3, 0, 0)),\n            37\n        );\n        assert_eq!(\n            wrapper.display_point_to_offset(WrapDisplayPoint::new(2, 1, 1)),\n            27\n        );\n        assert_eq!(\n            wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 5)),\n            21\n        );\n        assert_eq!(\n            wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 0)),\n            16\n        );\n        assert_eq!(\n            wrapper.display_point_to_offset(WrapDisplayPoint::new(0, 0, 15)),\n            15\n        );\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/display_map/wrap_map.rs",
    "content": "/// WrapMap: Soft-wrapping layer (Buffer → Wrap rows).\n///\n/// This module wraps the existing TextWrapper and provides:\n/// - BufferPoint ↔ WrapPoint mapping\n/// - Efficient buffer_line → wrap_row queries via prefix sum cache\n/// - Incremental updates when text or layout changes\nuse std::ops::Range;\n\nuse gpui::{App, Font, Pixels};\nuse ropey::Rope;\n\nuse super::fold_map::FoldMap;\nuse super::text_wrapper::{LineItem, TextWrapper, WrapDisplayPoint};\nuse super::{BufferPoint, WrapPoint};\nuse crate::input::rope_ext::RopeExt;\n\n/// WrapMap manages soft-wrapping and provides buffer ↔ wrap coordinate mapping.\npub struct WrapMap {\n    /// The underlying text wrapper (reuses existing implementation)\n    wrapper: TextWrapper,\n\n    /// Prefix sum cache: buffer_line_starts[line] = first wrap_row for buffer line `line`\n    /// This allows O(1) lookup of buffer_line → wrap_row\n    buffer_line_starts: Vec<usize>,\n\n    /// Cached line count from last rebuild\n    cached_line_count: usize,\n\n    /// Cached total wrap row count from last rebuild.\n    /// Used together with `cached_line_count` to detect if the cache is stale.\n    /// When soft wrap changes a line's wrap count without changing buffer line count,\n    /// this catches the staleness.\n    cached_wrap_row_count: usize,\n}\n\nimpl WrapMap {\n    pub fn new(font: Font, font_size: Pixels, wrap_width: Option<Pixels>) -> Self {\n        Self {\n            wrapper: TextWrapper::new(font, font_size, wrap_width),\n            buffer_line_starts: Vec::new(),\n            cached_line_count: 0,\n            cached_wrap_row_count: 0,\n        }\n    }\n\n    /// Get total number of wrap rows (visual rows after soft-wrapping)\n    #[inline]\n    pub fn wrap_row_count(&self) -> usize {\n        self.wrapper.len()\n    }\n\n    /// Get total number of buffer lines (logical lines)\n    #[inline]\n    pub fn buffer_line_count(&self) -> usize {\n        self.wrapper.lines.len()\n    }\n\n    /// Convert buffer position to wrap position\n    pub(super) fn buffer_pos_to_wrap_pos(&self, pos: BufferPoint) -> WrapPoint {\n        let BufferPoint { line, col } = pos;\n\n        // Clamp to valid range\n        let line = line.min(self.buffer_line_count().saturating_sub(1));\n        let line_item = self.wrapper.lines.get(line);\n\n        let col = if let Some(line_item) = line_item {\n            col.min(line_item.len())\n        } else {\n            0\n        };\n\n        // Calculate offset in rope\n        let line_start_offset = self.wrapper.text().line_start_offset(line);\n        let offset = line_start_offset + col;\n\n        // Use TextWrapper's existing conversion\n        let display_point = self.wrapper.offset_to_display_point(offset);\n\n        WrapPoint::new(display_point.row, display_point.column)\n    }\n\n    /// Convert wrap position to buffer position\n    pub(super) fn wrap_pos_to_buffer_pos(&self, pos: WrapPoint) -> BufferPoint {\n        let WrapPoint { row, col } = pos;\n\n        // Clamp wrap_row to valid range\n        let row = row.min(self.wrap_row_count().saturating_sub(1));\n\n        // Use TextWrapper's existing conversion\n        let display_point = WrapDisplayPoint::new(row, 0, col);\n        let offset = self.wrapper.display_point_to_offset(display_point);\n\n        // Convert offset to buffer position\n        let point = self.wrapper.text().offset_to_point(offset);\n        let line_start = self.wrapper.text().line_start_offset(point.row);\n        let col = offset.saturating_sub(line_start);\n\n        BufferPoint::new(point.row, col)\n    }\n\n    /// Get the buffer line for a given wrap row\n    pub fn wrap_row_to_buffer_line(&self, wrap_row: usize) -> usize {\n        if wrap_row >= self.wrap_row_count() {\n            return self.buffer_line_count().saturating_sub(1);\n        }\n\n        // Binary search in prefix sum cache\n        match self.buffer_line_starts.binary_search(&wrap_row) {\n            Ok(line) => line,\n            Err(insert_pos) => insert_pos.saturating_sub(1),\n        }\n    }\n\n    /// Get the first wrap row for a given buffer line\n    pub fn buffer_line_to_first_wrap_row(&self, line: usize) -> usize {\n        if line >= self.buffer_line_starts.len() {\n            return self.wrap_row_count();\n        }\n        self.buffer_line_starts[line]\n    }\n\n    /// Get the wrap row range for a buffer line: [start, end)\n    pub fn buffer_line_to_wrap_row_range(&self, line: usize) -> Range<usize> {\n        let start = self.buffer_line_to_first_wrap_row(line);\n        let end = if line + 1 < self.buffer_line_starts.len() {\n            self.buffer_line_starts[line + 1]\n        } else {\n            self.wrap_row_count()\n        };\n        start..end\n    }\n\n    /// Update text (incremental or full)\n    pub fn on_text_changed(\n        &mut self,\n        changed_text: &Rope,\n        range: &Range<usize>,\n        new_text: &Rope,\n        cx: &mut App,\n    ) {\n        self.wrapper.update(changed_text, range, new_text, cx);\n        self.rebuild_cache();\n    }\n\n    /// Update layout parameters (wrap width or font)\n    pub fn on_layout_changed(&mut self, wrap_width: Option<Pixels>, cx: &mut App) {\n        self.wrapper.set_wrap_width(wrap_width, cx);\n        self.rebuild_cache();\n    }\n\n    /// Set font parameters\n    pub fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) {\n        self.wrapper.set_font(font, font_size, cx);\n        self.rebuild_cache();\n    }\n\n    /// Ensure text is prepared (initializes wrapper if needed)\n    pub fn ensure_text_prepared(&mut self, text: &Rope, cx: &mut App) -> bool {\n        let did_initialize = self.wrapper.prepare_if_need(text, cx);\n        if did_initialize {\n            self.rebuild_cache();\n        }\n        did_initialize\n    }\n\n    /// Initialize with text\n    pub fn set_text(&mut self, text: &Rope, cx: &mut App) {\n        self.wrapper.set_default_text(text);\n        self.wrapper.prepare_if_need(text, cx);\n        self.rebuild_cache();\n    }\n\n    /// Rebuild the prefix sum cache: buffer_line_starts\n    fn rebuild_cache(&mut self) {\n        let line_count = self.wrapper.lines.len();\n        let wrap_row_count = self.wrapper.len();\n\n        // Skip if nothing changed: both buffer line count and total wrap row count must match.\n        // Checking wrap_row_count is essential because soft-wrap can change the number of\n        // wrap rows per line without changing the buffer line count.\n        if line_count == self.cached_line_count\n            && wrap_row_count == self.cached_wrap_row_count\n            && !self.buffer_line_starts.is_empty()\n        {\n            return;\n        }\n\n        self.buffer_line_starts.clear();\n\n        let mut wrap_row = 0;\n        for line_item in &self.wrapper.lines {\n            self.buffer_line_starts.push(wrap_row);\n            wrap_row += line_item.lines_len();\n        }\n\n        self.cached_line_count = line_count;\n        self.cached_wrap_row_count = wrap_row_count;\n    }\n\n    /// Get access to the underlying wrapper (for rendering/hit-testing)\n    pub(crate) fn wrapper(&self) -> &TextWrapper {\n        &self.wrapper\n    }\n\n    /// Get access to line items (for rendering)\n    pub(crate) fn lines(&self) -> &[LineItem] {\n        &self.wrapper.lines\n    }\n\n    /// Get the rope text\n    pub fn text(&self) -> &Rope {\n        self.wrapper.text()\n    }\n\n    /// Calculate how many wrap rows of a buffer line are visible (not folded)\n    pub fn visible_wrap_row_count_for_line(&self, line: usize, fold_map: &FoldMap) -> usize {\n        let wrap_range = self.buffer_line_to_wrap_row_range(line);\n        wrap_range\n            .filter(|&wr| fold_map.wrap_row_to_display_row(wr).is_some())\n            .count()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/element.rs",
    "content": "use std::{ops::Range, rc::Rc};\n\nuse gpui::{\n    App, Bounds, Corners, Element, ElementId, ElementInputHandler, Entity, GlobalElementId, Half,\n    HighlightStyle, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, LayoutId,\n    MouseButton, MouseMoveEvent, Path, Pixels, Point, ShapedLine, SharedString, Size, Style,\n    Styled as _, TextAlign, TextRun, TextStyle, UnderlineStyle, Window, fill, point, px, relative,\n    size,\n};\nuse ropey::Rope;\nuse smallvec::SmallVec;\n\nuse crate::{\n    ActiveTheme as _, Colorize, IconName, Root, Selectable, Sizable as _,\n    button::{Button, ButtonVariants as _},\n    input::{RopeExt as _, blink_cursor::CURSOR_WIDTH, display_map::LineLayout},\n};\n\nuse super::{InputState, LastLayout, WhitespaceIndicators, mode::InputMode};\n\nconst BOTTOM_MARGIN_ROWS: usize = 3;\npub(super) const RIGHT_MARGIN: Pixels = px(10.);\npub(super) const LINE_NUMBER_RIGHT_MARGIN: Pixels = px(10.);\nconst FOLD_ICON_WIDTH: Pixels = px(14.);\nconst FOLD_ICON_HITBOX_WIDTH: Pixels = px(18.);\nconst MAX_HIGHLIGHT_LINE_LENGTH: usize = 10_000;\n\n/// Layout information for fold icons.\nstruct FoldIconLayout {\n    /// Hitbox for the line number area (used for hover detection)\n    line_number_hitbox: Hitbox,\n    /// List of (display_row, is_folded, icon_element) pairs for each fold candidate\n    icons: Vec<(usize, bool, gpui::AnyElement)>,\n}\n\npub(super) struct TextElement {\n    pub(crate) state: Entity<InputState>,\n    placeholder: SharedString,\n}\n\nimpl TextElement {\n    pub(super) fn new(state: Entity<InputState>) -> Self {\n        Self {\n            state,\n            placeholder: SharedString::default(),\n        }\n    }\n\n    /// Set the placeholder text of the input field.\n    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {\n        self.placeholder = placeholder.into();\n        self\n    }\n\n    fn paint_mouse_listeners(&mut self, window: &mut Window, _: &mut App) {\n        window.on_mouse_event({\n            let state = self.state.clone();\n\n            move |event: &MouseMoveEvent, _, window, cx| {\n                if event.pressed_button == Some(MouseButton::Left) {\n                    state.update(cx, |state, cx| {\n                        state.on_drag_move(event, window, cx);\n                    });\n                }\n            }\n        });\n    }\n\n    /// Returns the:\n    ///\n    /// - cursor bounds\n    /// - scroll offset\n    /// - current row index (No only the visible lines, but all lines)\n    ///\n    /// This method also will update for track scroll to cursor.\n    fn layout_cursor(\n        &self,\n        last_layout: &LastLayout,\n        bounds: &mut Bounds<Pixels>,\n        _: &mut Window,\n        cx: &mut App,\n    ) -> (Option<Bounds<Pixels>>, Point<Pixels>, Option<usize>) {\n        let state = self.state.read(cx);\n\n        let line_height = last_layout.line_height;\n        let visible_range = &last_layout.visible_range;\n        let lines = &last_layout.lines;\n        let line_number_width = last_layout.line_number_width;\n\n        let mut selected_range = state.selected_range;\n\n        if let Some(ime_marked_range) = &state.ime_marked_range {\n            selected_range = (ime_marked_range.end..ime_marked_range.end).into();\n        }\n        let is_selected_all = selected_range.len() == state.text.len();\n\n        let mut cursor = state.cursor();\n        if state.masked {\n            // Because masked use `*`, 1 char with 1 byte.\n            selected_range.start = state.text.offset_to_char_index(selected_range.start);\n            selected_range.end = state.text.offset_to_char_index(selected_range.end);\n            cursor = state.text.offset_to_char_index(cursor);\n        }\n\n        let mut current_row = None;\n        let mut scroll_offset = state.scroll_handle.offset();\n        let mut cursor_bounds = None;\n\n        // If the input has a fixed height (Otherwise is auto-grow), we need to add a bottom margin to the input.\n        let top_bottom_margin = if state.mode.is_auto_grow() {\n            line_height\n        } else if visible_range.len() < BOTTOM_MARGIN_ROWS * 8 {\n            line_height\n        } else {\n            BOTTOM_MARGIN_ROWS * line_height\n        };\n\n        // The cursor corresponds to the current cursor position in the text no only the line.\n        let mut cursor_pos = None;\n        let mut cursor_start = None;\n        let mut cursor_end = None;\n\n        let mut prev_lines_offset = 0;\n        let mut offset_y = px(0.);\n        let buffer_lines = state.display_map.lines();\n        let visible_buffer_lines = &last_layout.visible_buffer_lines;\n        let mut vi = 0; // index into visible_buffer_lines / lines\n        for (ix, wrap_line) in buffer_lines.iter().enumerate() {\n            let row = ix;\n            let line_origin = point(px(0.), offset_y);\n\n            // break loop if all cursor positions are found\n            if cursor_pos.is_some() && cursor_start.is_some() && cursor_end.is_some() {\n                break;\n            }\n\n            // Check if this buffer line has a LineLayout in the compact lines vec\n            let line_layout = if vi < visible_buffer_lines.len() && visible_buffer_lines[vi] == ix {\n                let l = &lines[vi];\n                vi += 1;\n                Some(l)\n            } else {\n                None\n            };\n\n            if let Some(line) = line_layout {\n                if cursor_pos.is_none() {\n                    let offset = cursor.saturating_sub(prev_lines_offset);\n                    if let Some(pos) = line.position_for_index(offset, last_layout) {\n                        current_row = Some(row);\n                        cursor_pos = Some(line_origin + pos);\n                    }\n                }\n                if cursor_start.is_none() {\n                    let offset = selected_range.start.saturating_sub(prev_lines_offset);\n                    if let Some(pos) = line.position_for_index(offset, last_layout) {\n                        cursor_start = Some(line_origin + pos);\n                    }\n                }\n                if cursor_end.is_none() {\n                    let offset = selected_range.end.saturating_sub(prev_lines_offset);\n                    if let Some(pos) = line.position_for_index(offset, last_layout) {\n                        cursor_end = Some(line_origin + pos);\n                    }\n                }\n\n                offset_y += line.size(line_height).height;\n                // +1 for the last `\\n`\n                prev_lines_offset += wrap_line.len() + 1;\n            } else {\n                // Not visible (before visible range or hidden/folded).\n                // Just increase the offset_y and prev_lines_offset for scroll tracking.\n                if prev_lines_offset >= cursor && cursor_pos.is_none() {\n                    current_row = Some(row);\n                    cursor_pos = Some(line_origin);\n                }\n                if prev_lines_offset >= selected_range.start && cursor_start.is_none() {\n                    cursor_start = Some(line_origin);\n                }\n                if prev_lines_offset >= selected_range.end && cursor_end.is_none() {\n                    cursor_end = Some(line_origin);\n                }\n\n                let visible_wrap_rows =\n                    state.display_map.visible_wrap_row_count_for_buffer_line(ix);\n                offset_y += line_height * visible_wrap_rows;\n                // +1 for the last `\\n`\n                prev_lines_offset += wrap_line.len() + 1;\n            }\n        }\n\n        if let (Some(cursor_pos), Some(cursor_start), Some(cursor_end)) =\n            (cursor_pos, cursor_start, cursor_end)\n        {\n            let selection_changed = state.last_selected_range != Some(selected_range);\n            if selection_changed && !is_selected_all {\n                // Apart from left alignment, just leave enough space for the cursor size on the right side.\n                let safety_margin = if last_layout.text_align == TextAlign::Left {\n                    RIGHT_MARGIN\n                } else {\n                    CURSOR_WIDTH\n                };\n\n                scroll_offset.x = if scroll_offset.x + cursor_pos.x\n                    > (bounds.size.width - line_number_width - safety_margin)\n                {\n                    // cursor is out of right\n                    bounds.size.width - line_number_width - safety_margin - cursor_pos.x\n                } else if scroll_offset.x + cursor_pos.x < px(0.) {\n                    // cursor is out of left\n                    scroll_offset.x - cursor_pos.x\n                } else {\n                    scroll_offset.x\n                };\n\n                // If we change the scroll_offset.y, GPUI will render and trigger the next run loop.\n                // So, here we just adjust offset by `line_height` for move smooth.\n                scroll_offset.y =\n                    if scroll_offset.y + cursor_pos.y > bounds.size.height - top_bottom_margin {\n                        // cursor is out of bottom\n                        scroll_offset.y - line_height\n                    } else if scroll_offset.y + cursor_pos.y < top_bottom_margin {\n                        // cursor is out of top\n                        (scroll_offset.y + line_height).min(px(0.))\n                    } else {\n                        scroll_offset.y\n                    };\n\n                // For selection to move scroll\n                if state.selection_reversed {\n                    if scroll_offset.x + cursor_start.x < px(0.) {\n                        // selection start is out of left\n                        scroll_offset.x = -cursor_start.x;\n                    }\n                    if scroll_offset.y + cursor_start.y < px(0.) {\n                        // selection start is out of top\n                        scroll_offset.y = -cursor_start.y;\n                    }\n                } else {\n                    // TODO: Consider to remove this part,\n                    // maybe is not necessary (But selection_reversed is needed).\n                    if scroll_offset.x + cursor_end.x <= px(0.) {\n                        // selection end is out of left\n                        scroll_offset.x = -cursor_end.x;\n                    }\n                    if scroll_offset.y + cursor_end.y <= px(0.) {\n                        // selection end is out of top\n                        scroll_offset.y = -cursor_end.y;\n                    }\n                }\n            }\n\n            // cursor bounds\n            let cursor_height = match state.size {\n                crate::Size::Large => 1.,\n                crate::Size::Small => 0.75,\n                _ => 0.85,\n            } * line_height;\n\n            cursor_bounds = Some(Bounds::new(\n                point(\n                    bounds.left() + cursor_pos.x + line_number_width + scroll_offset.x,\n                    bounds.top() + cursor_pos.y + ((line_height - cursor_height) / 2.),\n                ),\n                size(CURSOR_WIDTH, cursor_height),\n            ));\n        }\n\n        if let Some(deferred_scroll_offset) = state.deferred_scroll_offset {\n            scroll_offset = deferred_scroll_offset;\n        }\n\n        bounds.origin = bounds.origin + scroll_offset;\n\n        (cursor_bounds, scroll_offset, current_row)\n    }\n\n    /// Layout the match range to a Path.\n    pub(crate) fn layout_match_range(\n        range: Range<usize>,\n        last_layout: &LastLayout,\n        bounds: &Bounds<Pixels>,\n    ) -> Option<Path<Pixels>> {\n        if range.is_empty() {\n            return None;\n        }\n\n        if range.start < last_layout.visible_range_offset.start\n            || range.end > last_layout.visible_range_offset.end\n        {\n            return None;\n        }\n\n        let line_height = last_layout.line_height;\n        let visible_top = last_layout.visible_top;\n        let lines = &last_layout.lines;\n        let line_number_width = last_layout.line_number_width;\n\n        let start_ix = range.start;\n        let end_ix = range.end;\n\n        // Start from visible_top (which already accounts for all lines before visible range)\n        let mut offset_y = visible_top;\n        let mut line_corners = vec![];\n\n        // Iterate only over visible (non-hidden) buffer lines\n        for (prev_lines_offset, line) in last_layout\n            .visible_line_byte_offsets\n            .iter()\n            .zip(lines.iter())\n        {\n            let prev_lines_offset = *prev_lines_offset;\n            let line_size = line.size(line_height);\n            let line_wrap_width = line_size.width;\n\n            let line_origin = point(px(0.), offset_y);\n\n            let line_cursor_start =\n                line.position_for_index(start_ix.saturating_sub(prev_lines_offset), last_layout);\n            let line_cursor_end =\n                line.position_for_index(end_ix.saturating_sub(prev_lines_offset), last_layout);\n\n            if line_cursor_start.is_some() || line_cursor_end.is_some() {\n                let start = line_cursor_start\n                    .unwrap_or_else(|| line.position_for_index(0, last_layout).unwrap());\n\n                let end = line_cursor_end\n                    .unwrap_or_else(|| line.position_for_index(line.len(), last_layout).unwrap());\n\n                // Split the selection into multiple items\n                let wrapped_lines =\n                    (end.y / line_height).ceil() as usize - (start.y / line_height).ceil() as usize;\n\n                let mut end_x = end.x;\n                if wrapped_lines > 0 {\n                    end_x = line_wrap_width;\n                }\n\n                // Ensure at least 6px width for the selection for empty lines.\n                end_x = end_x.max(start.x + px(6.));\n\n                line_corners.push(Corners {\n                    top_left: line_origin + point(start.x, start.y),\n                    top_right: line_origin + point(end_x, start.y),\n                    bottom_left: line_origin + point(start.x, start.y + line_height),\n                    bottom_right: line_origin + point(end_x, start.y + line_height),\n                });\n\n                // wrapped lines\n                for i in 1..=wrapped_lines {\n                    let start = point(px(0.), start.y + i as f32 * line_height);\n                    let mut end = point(end.x, end.y + i as f32 * line_height);\n                    if i < wrapped_lines {\n                        end.x = line_size.width;\n                    }\n\n                    line_corners.push(Corners {\n                        top_left: line_origin + point(start.x, start.y),\n                        top_right: line_origin + point(end.x, start.y),\n                        bottom_left: line_origin + point(start.x, start.y + line_height),\n                        bottom_right: line_origin + point(end.x, start.y + line_height),\n                    });\n                }\n            }\n\n            if line_cursor_start.is_some() && line_cursor_end.is_some() {\n                break;\n            }\n\n            offset_y += line_size.height;\n        }\n\n        let mut points = vec![];\n        if line_corners.is_empty() {\n            return None;\n        }\n\n        // Fix corners to make sure the left to right direction\n        for corners in &mut line_corners {\n            if corners.top_left.x > corners.top_right.x {\n                std::mem::swap(&mut corners.top_left, &mut corners.top_right);\n                std::mem::swap(&mut corners.bottom_left, &mut corners.bottom_right);\n            }\n        }\n\n        for corners in &line_corners {\n            points.push(corners.top_right);\n            points.push(corners.bottom_right);\n            points.push(corners.bottom_left);\n        }\n\n        let mut rev_line_corners = line_corners.iter().rev().peekable();\n        while let Some(corners) = rev_line_corners.next() {\n            points.push(corners.top_left);\n            if let Some(next) = rev_line_corners.peek() {\n                if next.top_left.x > corners.top_left.x {\n                    points.push(point(next.top_left.x, corners.top_left.y));\n                }\n            }\n        }\n\n        // print_points_as_svg_path(&line_corners, &points);\n\n        let path_origin = bounds.origin + point(line_number_width, px(0.));\n        let first_p = *points.get(0).unwrap();\n        let mut builder = gpui::PathBuilder::fill();\n        builder.move_to(path_origin + first_p);\n        for p in points.iter().skip(1) {\n            builder.line_to(path_origin + *p);\n        }\n\n        builder.build().ok()\n    }\n\n    fn layout_search_matches(\n        &self,\n        last_layout: &LastLayout,\n        bounds: &Bounds<Pixels>,\n        cx: &mut App,\n    ) -> Vec<(Path<Pixels>, bool)> {\n        let state = self.state.read(cx);\n        let search_panel = state.search_panel.clone();\n\n        let Some((ranges, current_match_ix)) = search_panel.and_then(|panel| {\n            if let Some(matcher) = panel.read(cx).matcher() {\n                Some((matcher.matched_ranges.clone(), matcher.current_match_ix))\n            } else {\n                None\n            }\n        }) else {\n            return vec![];\n        };\n\n        let mut paths = Vec::with_capacity(ranges.as_ref().len());\n        for (index, range) in ranges.as_ref().iter().enumerate() {\n            if let Some(path) = Self::layout_match_range(range.clone(), last_layout, bounds) {\n                paths.push((path, current_match_ix == index));\n            }\n        }\n\n        paths\n    }\n\n    fn layout_hover_highlight(\n        &self,\n        last_layout: &LastLayout,\n        bounds: &Bounds<Pixels>,\n        cx: &mut App,\n    ) -> Option<Path<Pixels>> {\n        let state = self.state.read(cx);\n        let hover_popover = state.hover_popover.clone();\n\n        let Some(symbol_range) = hover_popover.map(|popover| popover.read(cx).symbol_range.clone())\n        else {\n            return None;\n        };\n\n        Self::layout_match_range(symbol_range, last_layout, bounds)\n    }\n\n    fn layout_document_colors(\n        &self,\n        document_colors: &[(Range<usize>, Hsla)],\n        last_layout: &LastLayout,\n        bounds: &Bounds<Pixels>,\n        _cx: &mut App,\n    ) -> Vec<(Path<Pixels>, Hsla)> {\n        let mut paths = vec![];\n        for (range, color) in document_colors.iter() {\n            if let Some(path) = Self::layout_match_range(range.clone(), last_layout, bounds) {\n                paths.push((path, *color));\n            }\n        }\n\n        paths\n    }\n\n    fn layout_selections(\n        &self,\n        last_layout: &LastLayout,\n        bounds: &mut Bounds<Pixels>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Option<Path<Pixels>> {\n        let state = self.state.read(cx);\n        if !state.focus_handle.is_focused(window) {\n            return None;\n        }\n\n        let mut selected_range = state.selected_range;\n        if let Some(ime_marked_range) = &state.ime_marked_range {\n            if !ime_marked_range.is_empty() {\n                selected_range = (ime_marked_range.end..ime_marked_range.end).into();\n            }\n        }\n        if selected_range.is_empty() {\n            return None;\n        }\n\n        if state.masked {\n            // Because masked use `*`, 1 char with 1 byte.\n            selected_range.start = state.text.offset_to_char_index(selected_range.start);\n            selected_range.end = state.text.offset_to_char_index(selected_range.end);\n        }\n\n        let (start_ix, end_ix) = if selected_range.start < selected_range.end {\n            (selected_range.start, selected_range.end)\n        } else {\n            (selected_range.end, selected_range.start)\n        };\n\n        let range = start_ix.max(last_layout.visible_range_offset.start)\n            ..end_ix.min(last_layout.visible_range_offset.end);\n\n        Self::layout_match_range(range, &last_layout, bounds)\n    }\n\n    /// Calculate the visible range of lines in the viewport.\n    ///\n    /// Returns\n    ///\n    /// - visible_range: The visible range is based on unwrapped lines (Zero based).\n    /// - visible_buffer_lines: Indices of non-hidden buffer lines within the visible range.\n    /// - visible_top: The top position of the first visible line in the scroll viewport.\n    fn calculate_visible_range(\n        &self,\n        state: &InputState,\n        line_height: Pixels,\n        input_height: Pixels,\n    ) -> (Range<usize>, Vec<usize>, Pixels) {\n        // Add extra rows to avoid showing empty space when scroll to bottom.\n        let extra_rows = 1;\n        let mut visible_top = px(0.);\n        if state.mode.is_single_line() {\n            return (0..1, vec![0], visible_top);\n        }\n\n        let total_lines = state.display_map.wrap_row_count();\n        let scroll_top = if let Some(deferred_scroll_offset) = state.deferred_scroll_offset {\n            deferred_scroll_offset.y\n        } else {\n            state.scroll_handle.offset().y\n        };\n\n        let mut visible_range = 0..total_lines;\n        let mut line_bottom = px(0.);\n        for (ix, _line) in state.display_map.lines().iter().enumerate() {\n            let visible_wrap_rows = state.display_map.visible_wrap_row_count_for_buffer_line(ix);\n\n            if visible_wrap_rows == 0 {\n                continue;\n            }\n\n            let wrapped_height = line_height * visible_wrap_rows;\n            line_bottom += wrapped_height;\n\n            if line_bottom < -scroll_top {\n                visible_top = line_bottom - wrapped_height;\n                visible_range.start = ix;\n            }\n\n            if line_bottom + scroll_top >= input_height {\n                visible_range.end = (ix + extra_rows).min(total_lines);\n                break;\n            }\n        }\n\n        // Collect non-hidden buffer lines within the visible range\n        let mut visible_buffer_lines = Vec::with_capacity(visible_range.len());\n        for ix in visible_range.start..visible_range.end {\n            let visible_wrap_rows = state.display_map.visible_wrap_row_count_for_buffer_line(ix);\n            if visible_wrap_rows > 0 {\n                visible_buffer_lines.push(ix);\n            }\n        }\n\n        (visible_range, visible_buffer_lines, visible_top)\n    }\n\n    /// Return (line_number_width, line_number_len)\n    fn layout_line_numbers(\n        state: &InputState,\n        text: &Rope,\n        font_size: Pixels,\n        style: &TextStyle,\n        window: &mut Window,\n    ) -> (Pixels, usize) {\n        let total_lines = text.lines_len();\n        let line_number_len = match total_lines {\n            0..=9999 => 5,\n            10000..=99999 => 6,\n            100000..=999999 => 7,\n            _ => 8,\n        };\n\n        let mut line_number_width = if state.mode.line_number() {\n            let empty_line_number = window.text_system().shape_line(\n                \"+\".repeat(line_number_len).into(),\n                font_size,\n                &[TextRun {\n                    len: line_number_len,\n                    font: style.font(),\n                    color: gpui::black(),\n                    background_color: None,\n                    underline: None,\n                    strikethrough: None,\n                }],\n                None,\n            );\n\n            empty_line_number.width + LINE_NUMBER_RIGHT_MARGIN\n        } else if state.mode.is_code_editor() && state.mode.is_multi_line() {\n            LINE_NUMBER_RIGHT_MARGIN\n        } else {\n            px(0.)\n        };\n\n        if state.mode.is_folding() {\n            // Add extra space for fold icons\n            line_number_width += FOLD_ICON_HITBOX_WIDTH\n        }\n\n        (line_number_width, line_number_len)\n    }\n\n    /// Layout shaped lines for whitespace indicators (space and tab).\n    ///\n    /// Returns `WhitespaceIndicators` with shaped lines for space and tab characters.\n    fn layout_whitespace_indicators(\n        state: &InputState,\n        text_size: Pixels,\n        style: &TextStyle,\n        window: &mut Window,\n        cx: &App,\n    ) -> Option<WhitespaceIndicators> {\n        if !state.show_whitespaces {\n            return None;\n        }\n\n        let invisible_color = cx\n            .theme()\n            .highlight_theme\n            .style\n            .editor_invisible\n            .unwrap_or(cx.theme().muted_foreground);\n\n        let space_font_size = text_size.half();\n        let tab_font_size = text_size;\n\n        let space_text = SharedString::new_static(\"•\");\n        let space = window.text_system().shape_line(\n            space_text.clone(),\n            space_font_size,\n            &[TextRun {\n                len: space_text.len(),\n                font: style.font(),\n                color: invisible_color,\n                background_color: None,\n                underline: None,\n                strikethrough: None,\n            }],\n            None,\n        );\n\n        let tab_text = SharedString::new_static(\"→\");\n        let tab = window.text_system().shape_line(\n            tab_text.clone(),\n            tab_font_size,\n            &[TextRun {\n                len: tab_text.len(),\n                font: style.font(),\n                color: invisible_color,\n                background_color: None,\n                underline: None,\n                strikethrough: None,\n            }],\n            None,\n        );\n\n        Some(WhitespaceIndicators { space, tab })\n    }\n\n    /// Compute inline completion ghost lines for rendering.\n    ///\n    /// Returns (first_line, ghost_lines) where:\n    /// - first_line: Shaped text for the first line (goes after cursor on same line)\n    /// - ghost_lines: Shaped lines for subsequent lines (shift content down)\n    fn layout_inline_completion(\n        state: &InputState,\n        visible_range: &Range<usize>,\n        font_size: Pixels,\n        window: &mut Window,\n        cx: &App,\n    ) -> (Option<ShapedLine>, Vec<ShapedLine>) {\n        // Must be focused to show inline completion\n        if !state.focus_handle.is_focused(window) {\n            return (None, vec![]);\n        }\n\n        let Some(completion_item) = state.inline_completion.item.as_ref() else {\n            return (None, vec![]);\n        };\n\n        // Get cursor row from cursor position\n        let cursor_row = state.cursor_position().line as usize;\n\n        // Only show if cursor row is visible\n        if cursor_row < visible_range.start || cursor_row >= visible_range.end {\n            return (None, vec![]);\n        }\n\n        let completion_text = &completion_item.insert_text;\n        let completion_color = cx.theme().muted_foreground.opacity(0.5);\n\n        let text_style = window.text_style();\n        let font = text_style.font();\n\n        let lines: Vec<&str> = completion_text.split('\\n').collect();\n        if lines.is_empty() {\n            return (None, vec![]);\n        }\n\n        // Shape first line (goes after cursor)\n        let first_text: SharedString = lines[0].to_string().into();\n        let first_line = if !first_text.is_empty() {\n            let first_run = TextRun {\n                len: first_text.len(),\n                font: font.clone(),\n                color: completion_color,\n                background_color: None,\n                underline: None,\n                strikethrough: None,\n            };\n            Some(\n                window\n                    .text_system()\n                    .shape_line(first_text, font_size, &[first_run], None),\n            )\n        } else {\n            None\n        };\n\n        // Shape ghost lines (lines 2+ that shift content down)\n        let ghost_lines: Vec<ShapedLine> = lines[1..]\n            .iter()\n            .map(|line_text| {\n                let text: SharedString = line_text.to_string().into();\n                let len = text.len().max(1); // Ensure at least 1 for empty lines\n                let run = TextRun {\n                    len,\n                    font: font.clone(),\n                    color: completion_color,\n                    background_color: None,\n                    underline: None,\n                    strikethrough: None,\n                };\n                // Use space for empty lines so they take up height\n                let shaped_text = if text.is_empty() { \" \".into() } else { text };\n                window\n                    .text_system()\n                    .shape_line(shaped_text, font_size, &[run], None)\n            })\n            .collect();\n\n        (first_line, ghost_lines)\n    }\n\n    /// Return (line_number_width, line_number_len)\n    /// Layout fold icon hitboxes during prepaint phase.\n    ///\n    /// This creates hitboxes for the fold icon area, positioned to the right of line numbers.\n    /// Icons are created and prepainted here to avoid panics.\n    fn layout_fold_icons(\n        &self,\n        bounds: &Bounds<Pixels>,\n        last_layout: &LastLayout,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> FoldIconLayout {\n        // First pass: collect fold information from state\n        struct FoldInfo {\n            buffer_line: usize,\n            is_folded: bool,\n            display_row: usize,\n            offset_y: Pixels,\n        }\n\n        let line_number_hitbox = window.insert_hitbox(\n            Bounds::new(\n                bounds.origin + point(px(0.), last_layout.visible_top),\n                size(last_layout.line_number_width, bounds.size.height),\n            ),\n            HitboxBehavior::Normal,\n        );\n\n        let mut icon_layout = FoldIconLayout {\n            line_number_hitbox,\n            icons: vec![],\n        };\n\n        let fold_infos: Vec<FoldInfo> = {\n            let state = self.state.read(cx);\n            if !state.mode.is_folding() {\n                return icon_layout;\n            }\n\n            let mut infos = Vec::with_capacity(last_layout.visible_buffer_lines.len());\n            let mut offset_y = last_layout.visible_top;\n\n            for (line, &buffer_line) in last_layout\n                .lines\n                .iter()\n                .zip(last_layout.visible_buffer_lines.iter())\n            {\n                if state.display_map.is_fold_candidate(buffer_line) {\n                    let is_folded = state.display_map.is_folded_at(buffer_line);\n                    infos.push(FoldInfo {\n                        buffer_line,\n                        is_folded,\n                        display_row: buffer_line,\n                        offset_y,\n                    });\n                }\n\n                offset_y += line.wrapped_lines.len() * last_layout.line_height;\n            }\n\n            infos\n        }; // state is dropped here\n\n        // Second pass: create and prepaint icons\n        let line_height = last_layout.line_height;\n        let line_number_width = last_layout.line_number_width\n            - LINE_NUMBER_RIGHT_MARGIN.half()\n            - FOLD_ICON_HITBOX_WIDTH;\n        let icon_relative_pos = point(\n            (FOLD_ICON_HITBOX_WIDTH - FOLD_ICON_WIDTH).half(),\n            (line_height - FOLD_ICON_WIDTH).half(),\n        );\n\n        for (ix, info) in fold_infos.iter().enumerate() {\n            // Position fold icon to the right of line numbers\n            let fold_icon_bounds = Bounds::new(\n                bounds.origin + icon_relative_pos + point(line_number_width, info.offset_y),\n                size(FOLD_ICON_HITBOX_WIDTH, line_height),\n            );\n\n            // Create and prepaint icon\n            let mut icon = Button::new((\"fold\", ix))\n                .ghost()\n                .icon(if info.is_folded {\n                    IconName::ChevronRight\n                } else {\n                    IconName::ChevronDown\n                })\n                .xsmall()\n                .rounded_xs()\n                .size(FOLD_ICON_WIDTH)\n                .selected(info.is_folded)\n                .on_mouse_down(MouseButton::Left, {\n                    let state = self.state.clone();\n                    let buffer_line = info.buffer_line;\n                    move |_, _: &mut Window, cx: &mut App| {\n                        cx.stop_propagation();\n\n                        state.update(cx, |state, cx| {\n                            state.display_map.toggle_fold(buffer_line);\n                            cx.notify();\n                        });\n                    }\n                })\n                .into_any_element();\n\n            icon.prepaint_as_root(\n                fold_icon_bounds.origin,\n                fold_icon_bounds.size.into(),\n                window,\n                cx,\n            );\n\n            icon_layout\n                .icons\n                .push((info.display_row, info.is_folded, icon));\n        }\n\n        icon_layout\n    }\n\n    /// Paint fold icons using prepaint hitboxes.\n    ///\n    /// This handles:\n    /// - Rendering fold icons (chevron-right for folded, chevron-down for expanded)\n    /// - Mouse click handling to toggle fold state\n    /// - Cursor style changes on hover\n    /// - Only show icon on hover or for current line\n    fn paint_fold_icons(\n        &mut self,\n        fold_icon_layout: &mut FoldIconLayout,\n        current_row: Option<usize>,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        let is_hovered = fold_icon_layout.line_number_hitbox.is_hovered(window);\n        for (display_row, is_folded, icon) in fold_icon_layout.icons.iter_mut() {\n            let is_current_line = current_row == Some(*display_row);\n\n            if !is_hovered && !is_current_line && !*is_folded {\n                continue;\n            }\n\n            icon.paint(window, cx);\n        }\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    fn layout_lines(\n        state: &InputState,\n        display_text: &Rope,\n        last_layout: &LastLayout,\n        font_size: Pixels,\n        runs: &[TextRun],\n        bg_segments: &[(Range<usize>, Hsla)],\n        whitespace_indicators: Option<WhitespaceIndicators>,\n        window: &mut Window,\n    ) -> Vec<LineLayout> {\n        let is_single_line = state.mode.is_single_line();\n        let buffer_lines = state.display_map.lines();\n\n        if is_single_line {\n            let shaped_line = window.text_system().shape_line(\n                display_text.to_string().into(),\n                font_size,\n                &runs,\n                None,\n            );\n\n            let line_layout = LineLayout::new()\n                .lines(smallvec::smallvec![shaped_line])\n                .with_whitespaces(whitespace_indicators);\n            return vec![line_layout];\n        }\n\n        // Empty to use placeholder, the placeholder is not in the wrapper map.\n        if state.text.len() == 0 {\n            return display_text\n                .to_string()\n                .split(\"\\n\")\n                .map(|line| {\n                    let shaped_line = window.text_system().shape_line(\n                        line.to_string().into(),\n                        font_size,\n                        &runs,\n                        None,\n                    );\n                    LineLayout::new()\n                        .lines(smallvec::smallvec![shaped_line])\n                        .with_whitespaces(whitespace_indicators.clone())\n                })\n                .collect();\n        }\n\n        let mut lines = Vec::with_capacity(last_layout.visible_buffer_lines.len());\n        // run_offset tracks position in the runs vec coordinate space (only visible line bytes).\n        // This is separate from the visible_text offset because runs from highlight_lines\n        // only cover visible (non-folded) lines.\n        let mut run_offset = 0;\n\n        for (vi, &buffer_line) in last_layout.visible_buffer_lines.iter().enumerate() {\n            let line_text: String = display_text.slice_line(buffer_line).into();\n            let line_item = buffer_lines\n                .get(buffer_line)\n                .expect(\"line should exists in wrapper\");\n\n            debug_assert_eq!(line_item.len(), line_text.len());\n\n            let mut wrapped_lines = SmallVec::with_capacity(1);\n\n            for range in &line_item.wrapped_lines {\n                let line_runs = runs_for_range(runs, run_offset, &range);\n                let line_runs = if bg_segments.is_empty() {\n                    line_runs\n                } else {\n                    split_runs_by_bg_segments(\n                        last_layout.visible_line_byte_offsets[vi] + (range.start),\n                        &line_runs,\n                        bg_segments,\n                    )\n                };\n\n                let sub_line: SharedString = line_text[range.clone()].to_string().into();\n                let shaped_line = window\n                    .text_system()\n                    .shape_line(sub_line, font_size, &line_runs, None);\n\n                wrapped_lines.push(shaped_line);\n            }\n\n            let line_layout = LineLayout::new()\n                .lines(wrapped_lines)\n                .with_whitespaces(whitespace_indicators.clone());\n            lines.push(line_layout);\n\n            // +1 for the `\\n`\n            run_offset += line_text.len() + 1;\n        }\n\n        lines\n    }\n\n    /// First usize is the offset of skipped.\n    fn highlight_lines(\n        &mut self,\n        visible_buffer_lines: &[usize],\n        _visible_top: Pixels,\n        visible_byte_range: Range<usize>,\n        cx: &mut App,\n    ) -> Option<Vec<(Range<usize>, HighlightStyle)>> {\n        let state = self.state.read(cx);\n        let text = &state.text;\n        let is_multi_line = state.mode.is_multi_line();\n\n        let (mut highlighter, diagnostics) = match &state.mode {\n            InputMode::CodeEditor {\n                highlighter,\n                diagnostics,\n                ..\n            } => (highlighter.borrow_mut(), diagnostics),\n            _ => return None,\n        };\n        let highlighter = highlighter.as_mut()?;\n\n        let mut styles = Vec::with_capacity(visible_buffer_lines.len());\n\n        // Helper to flush a contiguous range of lines\n        let flush_range = |start_line: usize, end_line: usize, skip: bool, styles: &mut Vec<_>| {\n            let byte_start = text.line_start_offset(start_line);\n            let byte_end = if is_multi_line {\n                // +1 for `\\n`\n                text.line_start_offset(end_line + 1)\n            } else {\n                text.line_end_offset(end_line)\n            };\n            let range_styles = if skip {\n                vec![(byte_start..byte_end, HighlightStyle::default())]\n            } else {\n                highlighter.styles(&(byte_start..byte_end), &cx.theme().highlight_theme)\n            };\n\n            *styles = gpui::combine_highlights(styles.clone(), range_styles).collect();\n        };\n\n        // Group contiguous visible lines into ranges and call styles() once per range\n        let mut visible_iter = visible_buffer_lines.iter().peekable();\n        let mut range_start: Option<usize> = None;\n\n        while let Some(&line) = visible_iter.next() {\n            // Check if this line is too long for highlighting\n            let line_len = text.slice_line(line).len();\n            if line_len > MAX_HIGHLIGHT_LINE_LENGTH {\n                // Flush any accumulated range first\n                if let Some(start) = range_start.take() {\n                    flush_range(start, line - 1, false, &mut styles);\n                }\n\n                flush_range(line, line, true, &mut styles);\n                continue;\n            }\n\n            range_start.get_or_insert(line);\n\n            // Check if next line is contiguous, if so keep accumulating\n            if visible_iter\n                .peek()\n                .map(|&&next| next == line + 1)\n                .unwrap_or(false)\n            {\n                continue;\n            }\n\n            // Flush the contiguous range\n            let start_line = range_start.take().unwrap();\n            flush_range(start_line, line, false, &mut styles);\n        }\n\n        let diagnostic_styles = diagnostics.styles_for_range(&visible_byte_range, cx);\n\n        // hover definition style\n        if let Some(hover_style) = self.layout_hover_definition(cx) {\n            styles.push(hover_style);\n        }\n\n        // Combine marker styles\n        styles = gpui::combine_highlights(diagnostic_styles, styles).collect();\n\n        Some(styles)\n    }\n}\n\npub(super) struct PrepaintState {\n    /// The lines of entire lines.\n    last_layout: LastLayout,\n    /// The lines only contains the visible lines in the viewport, based on `visible_range`.\n    ///\n    /// The child is the soft lines.\n    line_numbers: Option<Vec<SmallVec<[ShapedLine; 1]>>>,\n    /// Size of the scrollable area by entire lines.\n    scroll_size: Size<Pixels>,\n    cursor_bounds: Option<Bounds<Pixels>>,\n    cursor_scroll_offset: Point<Pixels>,\n    /// row index (zero based), no wrap, same line as the cursor.\n    current_row: Option<usize>,\n    selection_path: Option<Path<Pixels>>,\n    hover_highlight_path: Option<Path<Pixels>>,\n    search_match_paths: Vec<(Path<Pixels>, bool)>,\n    document_color_paths: Vec<(Path<Pixels>, Hsla)>,\n    hover_definition_hitbox: Option<Hitbox>,\n    indent_guides_path: Option<Path<Pixels>>,\n    bounds: Bounds<Pixels>,\n    /// Fold icon layout data\n    fold_icon_layout: FoldIconLayout,\n    // Inline completion rendering data\n    /// Shaped ghost lines to paint after cursor row (completion lines 2+)\n    ghost_lines: Vec<ShapedLine>,\n    /// First line of inline completion (painted after cursor on same line)\n    ghost_first_line: Option<ShapedLine>,\n    ghost_lines_height: Pixels,\n}\n\nimpl PrepaintState {\n    /// Returns cursor bounds adjusted for scroll offset, if available.\n    fn cursor_bounds_with_scroll(&self) -> Option<Bounds<Pixels>> {\n        self.cursor_bounds.map(|mut bounds| {\n            bounds.origin.y += self.cursor_scroll_offset.y;\n            bounds\n        })\n    }\n}\n\nimpl IntoElement for TextElement {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\n/// A debug function to print points as SVG path.\n#[allow(unused)]\nfn print_points_as_svg_path(\n    line_corners: &Vec<Corners<Point<Pixels>>>,\n    points: &Vec<Point<Pixels>>,\n) {\n    for corners in line_corners {\n        println!(\n            \"tl: ({}, {}), tr: ({}, {}), bl: ({}, {}), br: ({}, {})\",\n            corners.top_left.x.as_f32() as i32,\n            corners.top_left.y.as_f32() as i32,\n            corners.top_right.x.as_f32() as i32,\n            corners.top_right.y.as_f32() as i32,\n            corners.bottom_left.x.as_f32() as i32,\n            corners.bottom_left.y.as_f32() as i32,\n            corners.bottom_right.x.as_f32() as i32,\n            corners.bottom_right.y.as_f32() as i32,\n        );\n    }\n\n    if points.len() > 0 {\n        println!(\n            \"M{},{}\",\n            points[0].x.as_f32() as i32,\n            points[0].y.as_f32() as i32\n        );\n        for p in points.iter().skip(1) {\n            println!(\"L{},{}\", p.x.as_f32() as i32, p.y.as_f32() as i32);\n        }\n    }\n}\n\nimpl Element for TextElement {\n    type RequestLayoutState = ();\n    type PrepaintState = PrepaintState;\n\n    fn id(&self) -> Option<ElementId> {\n        None\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        _id: Option<&GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (LayoutId, Self::RequestLayoutState) {\n        let state = self.state.read(cx);\n        let line_height = window.line_height();\n\n        let mut style = Style::default();\n        style.size.width = relative(1.).into();\n        if state.mode.is_multi_line() {\n            style.flex_grow = 1.0;\n            style.size.height = relative(1.).into();\n            if state.mode.is_auto_grow() {\n                // Auto grow to let height match to rows, but not exceed max rows.\n                let rows = state.mode.max_rows().min(state.mode.rows());\n                style.min_size.height = (rows * line_height).into();\n            } else {\n                style.min_size.height = line_height.into();\n            }\n        } else {\n            // For single-line inputs, the minimum height should be the line height\n            style.size.height = line_height.into();\n        };\n\n        (window.request_layout(style, [], cx), ())\n    }\n\n    fn prepaint(\n        &mut self,\n        _id: Option<&GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        _request_layout: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::PrepaintState {\n        let style = window.text_style();\n        let font = style.font();\n        let text_size = style.font_size.to_pixels(window.rem_size());\n\n        self.state.update(cx, |state, cx| {\n            state.display_map.set_font(font, text_size, cx);\n            state.display_map.ensure_text_prepared(&state.text, cx);\n        });\n\n        let state = self.state.read(cx);\n        let line_height = window.line_height();\n\n        let (visible_range, visible_buffer_lines, visible_top) =\n            self.calculate_visible_range(&state, line_height, bounds.size.height);\n        let visible_start_offset = state.text.line_start_offset(visible_range.start);\n        let visible_end_offset = state\n            .text\n            .line_end_offset(visible_range.end.saturating_sub(1));\n\n        let highlight_styles = self.highlight_lines(\n            &visible_buffer_lines,\n            visible_top,\n            visible_start_offset..visible_end_offset,\n            cx,\n        );\n\n        let state = self.state.read(cx);\n        let multi_line = state.mode.is_multi_line();\n        let text = state.text.clone();\n        let is_empty = text.len() == 0;\n        let placeholder = self.placeholder.clone();\n\n        let mut bounds = bounds;\n\n        let (display_text, text_color) = if is_empty {\n            (\n                &Rope::from(placeholder.as_str()),\n                cx.theme().muted_foreground,\n            )\n        } else if state.masked {\n            (\n                &Rope::from(\"*\".repeat(text.chars().count())),\n                cx.theme().foreground,\n            )\n        } else {\n            (&text, cx.theme().foreground)\n        };\n\n        let text_style = window.text_style();\n\n        // Calculate the width of the line numbers\n        let (line_number_width, line_number_len) =\n            Self::layout_line_numbers(&state, &text, text_size, &text_style, window);\n\n        let wrap_width = if multi_line && state.soft_wrap {\n            Some(bounds.size.width - line_number_width - RIGHT_MARGIN)\n        } else {\n            None\n        };\n\n        let visible_line_byte_offsets: Vec<usize> = visible_buffer_lines\n            .iter()\n            .map(|&bl| state.text.line_start_offset(bl))\n            .collect();\n\n        let mut last_layout = LastLayout {\n            visible_range,\n            visible_buffer_lines,\n            visible_line_byte_offsets,\n            visible_top,\n            visible_range_offset: visible_start_offset..visible_end_offset,\n            line_height,\n            wrap_width,\n            line_number_width,\n            lines: Rc::new(vec![]),\n            cursor_bounds: None,\n            text_align: state.text_align,\n            content_width: bounds.size.width,\n        };\n\n        let run = TextRun {\n            len: display_text.len(),\n            font: style.font(),\n            color: text_color,\n            background_color: None,\n            underline: None,\n            strikethrough: None,\n        };\n        let marked_run = TextRun {\n            len: 0,\n            font: style.font(),\n            color: text_color,\n            background_color: None,\n            underline: Some(UnderlineStyle {\n                thickness: px(1.),\n                color: Some(text_color),\n                wavy: false,\n            }),\n            strikethrough: None,\n        };\n\n        let runs = if !is_empty {\n            if let Some(highlight_styles) = highlight_styles {\n                let mut runs = Vec::with_capacity(highlight_styles.len());\n\n                runs.extend(highlight_styles.iter().map(|(range, style)| {\n                    let mut run = text_style.clone().highlight(*style).to_run(range.len());\n                    if let Some(ime_marked_range) = &state.ime_marked_range {\n                        if range.start >= ime_marked_range.start\n                            && range.end <= ime_marked_range.end\n                        {\n                            run.color = marked_run.color;\n                            run.strikethrough = marked_run.strikethrough;\n                            run.underline = marked_run.underline;\n                        }\n                    }\n\n                    run\n                }));\n\n                runs.into_iter().filter(|run| run.len > 0).collect()\n            } else {\n                vec![run]\n            }\n        } else if let Some(ime_marked_range) = &state.ime_marked_range {\n            // IME marked text\n            vec![\n                TextRun {\n                    len: ime_marked_range.start,\n                    ..run.clone()\n                },\n                TextRun {\n                    len: ime_marked_range.end - ime_marked_range.start,\n                    underline: marked_run.underline,\n                    ..run.clone()\n                },\n                TextRun {\n                    len: display_text.len() - ime_marked_range.end,\n                    ..run.clone()\n                },\n            ]\n            .into_iter()\n            .filter(|run| run.len > 0)\n            .collect()\n        } else {\n            vec![run]\n        };\n\n        let document_colors = state\n            .lsp\n            .document_colors_for_range(&text, &last_layout.visible_range);\n\n        // Create shaped lines for whitespace indicators before layout\n        let whitespace_indicators =\n            Self::layout_whitespace_indicators(&state, text_size, &text_style, window, cx);\n\n        let lines = Self::layout_lines(\n            &state,\n            &display_text,\n            &last_layout,\n            text_size,\n            &runs,\n            &document_colors,\n            whitespace_indicators,\n            window,\n        );\n\n        let mut longest_line_width = wrap_width.unwrap_or(px(0.));\n        // 1. Single line\n        // 2. Multi-line with soft wrap disabled.\n        if state.mode.is_single_line() || !state.soft_wrap {\n            let longest_row = state.display_map.longest_row();\n            let longest_line: SharedString = state.text.slice_line(longest_row).to_string().into();\n            longest_line_width = window\n                .text_system()\n                .shape_line(\n                    longest_line.clone(),\n                    text_size,\n                    &[TextRun {\n                        len: longest_line.len(),\n                        font: style.font(),\n                        color: gpui::black(),\n                        background_color: None,\n                        underline: None,\n                        strikethrough: None,\n                    }],\n                    wrap_width,\n                )\n                .width;\n        }\n        last_layout.lines = Rc::new(lines);\n\n        let (ghost_first_line, ghost_lines) = Self::layout_inline_completion(\n            state,\n            &last_layout.visible_range,\n            text_size,\n            window,\n            cx,\n        );\n        let ghost_line_count = ghost_lines.len();\n        let ghost_lines_height = ghost_line_count as f32 * line_height;\n\n        let total_wrapped_lines = state.display_map.wrap_row_count();\n        let empty_bottom_height = if state.mode.is_code_editor() {\n            bounds\n                .size\n                .height\n                .half()\n                .max(BOTTOM_MARGIN_ROWS * line_height)\n        } else {\n            px(0.)\n        };\n\n        let mut scroll_size = size(\n            if longest_line_width + line_number_width + RIGHT_MARGIN > bounds.size.width {\n                longest_line_width + line_number_width + RIGHT_MARGIN\n            } else {\n                longest_line_width\n            },\n            (total_wrapped_lines as f32 * line_height + empty_bottom_height + ghost_lines_height)\n                .max(bounds.size.height),\n        );\n\n        // TODO: should be add some gap to right, to convenient to focus on boundary position\n        if last_layout.text_align == TextAlign::Right || last_layout.text_align == TextAlign::Center\n        {\n            scroll_size.width = longest_line_width + line_number_width;\n        }\n\n        // `position_for_index` for example\n        //\n        // #### text\n        //\n        // Hello 世界，this is GPUI component.\n        // The GPUI Component is a collection of UI components for\n        // GPUI framework, including Button, Input, Checkbox, Radio,\n        // Dropdown, Tab, and more...\n        //\n        // wrap_width: 444px, line_height: 20px\n        //\n        // #### lines[0]\n        //\n        // | index | pos              | line |\n        // |-------|------------------|------|\n        // | 5     | (37 px, 0.0)     | 0    |\n        // | 38    | (261.7 px, 20.0) | 0    |\n        // | 40    | None             | -    |\n        //\n        // #### lines[1]\n        //\n        // | index | position              | line |\n        // |-------|-----------------------|------|\n        // | 5     | (43.578125 px, 0.0)   | 0    |\n        // | 56    | (422.21094 px, 0.0)   | 0    |\n        // | 57    | (11.6328125 px, 20.0) | 1    |\n        // | 114   | (429.85938 px, 20.0)  | 1    |\n        // | 115   | (11.3125 px, 40.0)    | 2    |\n\n        // Calculate the scroll offset to keep the cursor in view\n\n        let (cursor_bounds, cursor_scroll_offset, current_row) =\n            self.layout_cursor(&last_layout, &mut bounds, window, cx);\n        last_layout.cursor_bounds = cursor_bounds;\n\n        let search_match_paths = self.layout_search_matches(&last_layout, &mut bounds, cx);\n        let selection_path = self.layout_selections(&last_layout, &mut bounds, window, cx);\n        let hover_highlight_path = self.layout_hover_highlight(&last_layout, &mut bounds, cx);\n        let document_color_paths =\n            self.layout_document_colors(&document_colors, &last_layout, &bounds, cx);\n\n        let state = self.state.read(cx);\n        let line_numbers = if state.mode.line_number() {\n            let mut line_numbers = Vec::with_capacity(last_layout.visible_buffer_lines.len());\n            let other_line_runs = vec![TextRun {\n                len: line_number_len,\n                font: style.font(),\n                color: cx.theme().muted_foreground,\n                background_color: None,\n                underline: None,\n                strikethrough: None,\n            }];\n            let current_line_runs = vec![TextRun {\n                len: line_number_len,\n                font: style.font(),\n                color: cx.theme().foreground,\n                background_color: None,\n                underline: None,\n                strikethrough: None,\n            }];\n\n            // build line numbers\n            for (line, &buffer_line) in last_layout\n                .lines\n                .iter()\n                .zip(last_layout.visible_buffer_lines.iter())\n            {\n                let line_no: SharedString =\n                    format!(\"{:>width$}\", buffer_line + 1, width = line_number_len).into();\n\n                let runs = if current_row == Some(buffer_line) {\n                    &current_line_runs\n                } else {\n                    &other_line_runs\n                };\n\n                let mut sub_lines: SmallVec<[ShapedLine; 1]> = SmallVec::new();\n                sub_lines.push(\n                    window\n                        .text_system()\n                        .shape_line(line_no, text_size, &runs, None),\n                );\n                for _ in 0..line.wrapped_lines.len().saturating_sub(1) {\n                    sub_lines.push(ShapedLine::default());\n                }\n                line_numbers.push(sub_lines);\n            }\n            Some(line_numbers)\n        } else {\n            None\n        };\n\n        let hover_definition_hitbox = self.layout_hover_definition_hitbox(state, window, cx);\n        let indent_guides_path =\n            self.layout_indent_guides(state, &bounds, &last_layout, &text_style, window);\n        let fold_icon_layout = self.layout_fold_icons(&bounds, &last_layout, window, cx);\n\n        PrepaintState {\n            bounds,\n            last_layout,\n            scroll_size,\n            line_numbers,\n            cursor_bounds,\n            cursor_scroll_offset,\n            current_row,\n            selection_path,\n            search_match_paths,\n            hover_highlight_path,\n            hover_definition_hitbox,\n            document_color_paths,\n            indent_guides_path,\n            fold_icon_layout,\n            ghost_first_line,\n            ghost_lines,\n            ghost_lines_height,\n        }\n    }\n\n    fn paint(\n        &mut self,\n        _id: Option<&GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        input_bounds: Bounds<Pixels>,\n        _request_layout: &mut Self::RequestLayoutState,\n        prepaint: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        let focus_handle = self.state.read(cx).focus_handle.clone();\n        let show_cursor = self.state.read(cx).show_cursor(window, cx);\n        let focused = focus_handle.is_focused(window);\n        let bounds = prepaint.bounds;\n        let selected_range = self.state.read(cx).selected_range;\n        let text_align = prepaint.last_layout.text_align;\n\n        window.handle_input(\n            &focus_handle,\n            ElementInputHandler::new(bounds, self.state.clone()),\n            cx,\n        );\n\n        // Set Root focused_input when self is focused\n        if focused {\n            let state = self.state.clone();\n            if Root::read(window, cx).focused_input.as_ref() != Some(&state) {\n                Root::update(window, cx, |root, _, cx| {\n                    root.focused_input = Some(state);\n                    cx.notify();\n                });\n            }\n        }\n\n        // And reset focused_input when next_frame start\n        window.on_next_frame({\n            let state = self.state.clone();\n            move |window, cx| {\n                if !focused && Root::read(window, cx).focused_input.as_ref() == Some(&state) {\n                    Root::update(window, cx, |root, _, cx| {\n                        root.focused_input = None;\n                        cx.notify();\n                    });\n                }\n            }\n        });\n\n        // Paint multi line text\n        let line_height = window.line_height();\n        let origin = bounds.origin;\n\n        let invisible_top_padding = prepaint.last_layout.visible_top;\n\n        let mut mask_offset_y = px(0.);\n        let state = self.state.read(cx);\n        if state.masked && state.text.len() > 0 {\n            // Move down offset for vertical centering the *****\n            if cfg!(target_os = \"macos\") {\n                mask_offset_y = px(3.);\n            } else {\n                mask_offset_y = px(2.5);\n            }\n        }\n        let active_line_color = cx.theme().highlight_theme.style.editor_active_line;\n\n        // Paint active line\n        let mut offset_y = px(0.);\n        if let Some(line_numbers) = prepaint.line_numbers.as_ref() {\n            offset_y += invisible_top_padding;\n\n            // Each item is the normal lines.\n            for (lines, &buffer_line) in line_numbers\n                .iter()\n                .zip(prepaint.last_layout.visible_buffer_lines.iter())\n            {\n                let is_active = prepaint.current_row == Some(buffer_line);\n                let p = point(input_bounds.origin.x, origin.y + offset_y);\n                let height = line_height * lines.len() as f32;\n                // Paint the current line background\n                if is_active {\n                    if let Some(bg_color) = active_line_color {\n                        window.paint_quad(fill(\n                            Bounds::new(p, size(bounds.size.width, height)),\n                            bg_color,\n                        ));\n                    }\n                }\n                offset_y += height;\n            }\n        }\n\n        // Paint indent guides\n        if let Some(path) = prepaint.indent_guides_path.take() {\n            window.paint_path(path, cx.theme().border.opacity(0.85));\n        }\n\n        // Paint selections\n        if window.is_window_active() {\n            let secondary_selection = cx.theme().selection.saturation(0.1);\n            for (path, is_active) in prepaint.search_match_paths.iter() {\n                window.paint_path(path.clone(), secondary_selection);\n\n                if *is_active {\n                    window.paint_path(path.clone(), cx.theme().selection);\n                }\n            }\n\n            if let Some(path) = prepaint.selection_path.take() {\n                window.paint_path(path, cx.theme().selection);\n            }\n\n            // Paint hover highlight\n            if let Some(path) = prepaint.hover_highlight_path.take() {\n                window.paint_path(path, secondary_selection);\n            }\n        }\n\n        // Paint document colors\n        for (path, color) in prepaint.document_color_paths.iter() {\n            window.paint_path(path.clone(), *color);\n        }\n\n        // Paint text with inline completion ghost line support\n        let mut offset_y = mask_offset_y + invisible_top_padding;\n        let ghost_lines = &prepaint.ghost_lines;\n        let has_ghost_lines = !ghost_lines.is_empty();\n\n        // Keep scrollbar offset always be positive，Start from the left position\n        let scroll_offset = if text_align == TextAlign::Right {\n            (prepaint.scroll_size.width - prepaint.bounds.size.width).max(px(0.))\n        } else if text_align == TextAlign::Center {\n            (prepaint.scroll_size.width - prepaint.bounds.size.width)\n                .half()\n                .max(px(0.))\n        } else {\n            px(0.)\n        };\n\n        // Track the y-position of the cursor row for positioning the first line suffix\n        let mut cursor_row_y = None;\n\n        for (line, &buffer_line) in prepaint\n            .last_layout\n            .lines\n            .iter()\n            .zip(prepaint.last_layout.visible_buffer_lines.iter())\n        {\n            let row = buffer_line;\n            let line_y = origin.y + offset_y;\n            let p = point(\n                origin.x + prepaint.last_layout.line_number_width + (scroll_offset),\n                line_y,\n            );\n\n            // Paint the actual line\n            _ = line.paint(\n                p,\n                line_height,\n                text_align,\n                Some(prepaint.last_layout.content_width),\n                window,\n                cx,\n            );\n            offset_y += line.size(line_height).height;\n\n            if Some(row) == prepaint.current_row {\n                cursor_row_y = Some(line_y);\n            }\n\n            // After the cursor row, paint ghost lines (which shifts subsequent content down)\n            if has_ghost_lines && Some(row) == prepaint.current_row {\n                let ghost_x = origin.x + prepaint.last_layout.line_number_width;\n\n                for ghost_line in ghost_lines {\n                    let ghost_p = point(ghost_x, origin.y + offset_y);\n\n                    // Paint semi-transparent background for ghost line\n                    let ghost_bounds = Bounds::new(\n                        ghost_p,\n                        size(\n                            bounds.size.width - prepaint.last_layout.line_number_width,\n                            line_height,\n                        ),\n                    );\n                    window.paint_quad(fill(ghost_bounds, cx.theme().editor_background()));\n\n                    // Paint ghost line text\n                    _ = ghost_line.paint(\n                        ghost_p,\n                        line_height,\n                        text_align,\n                        Some(prepaint.last_layout.content_width),\n                        window,\n                        cx,\n                    );\n                    offset_y += line_height;\n                }\n            }\n        }\n\n        // Paint blinking cursor\n        if focused && show_cursor {\n            if let Some(cursor_bounds) = prepaint.cursor_bounds_with_scroll() {\n                window.paint_quad(fill(cursor_bounds, cx.theme().caret));\n            }\n        }\n\n        // Paint line numbers\n        let mut offset_y = px(0.);\n        if let Some(line_numbers) = prepaint.line_numbers.as_ref() {\n            offset_y += invisible_top_padding;\n\n            window.paint_quad(fill(\n                Bounds {\n                    origin: input_bounds.origin,\n                    size: size(\n                        prepaint.last_layout.line_number_width - LINE_NUMBER_RIGHT_MARGIN,\n                        input_bounds.size.height + prepaint.ghost_lines_height,\n                    ),\n                },\n                cx.theme().editor_background(),\n            ));\n\n            // Each item is the normal lines.\n            for (lines, &buffer_line) in line_numbers\n                .iter()\n                .zip(prepaint.last_layout.visible_buffer_lines.iter())\n            {\n                let p = point(input_bounds.origin.x, origin.y + offset_y);\n                let is_active = prepaint.current_row == Some(buffer_line);\n\n                let height = line_height * lines.len() as f32;\n                // paint active line number background\n                if is_active {\n                    if let Some(bg_color) = active_line_color {\n                        window.paint_quad(fill(\n                            Bounds::new(p, size(prepaint.last_layout.line_number_width, height)),\n                            bg_color,\n                        ));\n                    }\n                }\n\n                for line in lines {\n                    _ = line.paint(p, line_height, TextAlign::Left, None, window, cx);\n                    offset_y += line_height;\n                }\n\n                // Add ghost line height after cursor row for line numbers alignment\n                if !prepaint.ghost_lines.is_empty() && prepaint.current_row == Some(buffer_line) {\n                    offset_y += prepaint.ghost_lines_height;\n                }\n            }\n        }\n\n        // Paint fold icons (only visible on hover or for current line)\n        self.paint_fold_icons(\n            &mut prepaint.fold_icon_layout,\n            prepaint.current_row,\n            window,\n            cx,\n        );\n\n        self.state.update(cx, |state, cx| {\n            state.last_layout = Some(prepaint.last_layout.clone());\n            state.last_bounds = Some(bounds);\n            state.last_cursor = Some(state.cursor());\n            state.set_input_bounds(input_bounds, cx);\n            state.last_selected_range = Some(selected_range);\n            state.scroll_size = prepaint.scroll_size;\n            state.update_scroll_offset(Some(prepaint.cursor_scroll_offset), cx);\n            state.deferred_scroll_offset = None;\n\n            cx.notify();\n        });\n\n        if let Some(hitbox) = prepaint.hover_definition_hitbox.as_ref() {\n            window.set_cursor_style(gpui::CursorStyle::PointingHand, &hitbox);\n        }\n\n        // Paint inline completion first line suffix (after cursor on same line)\n        if focused {\n            if let Some(first_line) = &prepaint.ghost_first_line {\n                if let (Some(cursor_bounds), Some(cursor_row_y)) =\n                    (prepaint.cursor_bounds_with_scroll(), cursor_row_y)\n                {\n                    let first_line_x = cursor_bounds.origin.x + cursor_bounds.size.width;\n                    let p = point(first_line_x, cursor_row_y);\n\n                    // Paint background to cover any existing text\n                    let bg_bounds = Bounds::new(p, size(first_line.width + px(4.), line_height));\n                    window.paint_quad(fill(bg_bounds, cx.theme().editor_background()));\n\n                    // Paint first line completion text\n                    _ = first_line.paint(p, line_height, text_align, None, window, cx);\n                }\n            }\n        }\n\n        self.paint_mouse_listeners(window, cx);\n    }\n}\n\n/// Get the runs for the given range.\n///\n/// The range is the byte range of the wrapped line.\npub(super) fn runs_for_range(\n    runs: &[TextRun],\n    line_offset: usize,\n    range: &Range<usize>,\n) -> Vec<TextRun> {\n    let mut result = vec![];\n    let range = (line_offset + range.start)..(line_offset + range.end);\n    let mut cursor = 0;\n\n    for run in runs {\n        let run_start = cursor;\n        let run_end = cursor + run.len;\n\n        if run_end <= range.start {\n            cursor = run_end;\n            continue;\n        }\n\n        if run_start >= range.end {\n            break;\n        }\n\n        let start = range.start.max(run_start) - run_start;\n        let end = range.end.min(run_end) - run_start;\n        let len = end - start;\n\n        if len > 0 {\n            result.push(TextRun { len, ..run.clone() });\n        }\n\n        cursor = run_end;\n    }\n\n    result\n}\n\nfn split_runs_by_bg_segments(\n    start_offset: usize,\n    runs: &[TextRun],\n    bg_segments: &[(Range<usize>, Hsla)],\n) -> Vec<TextRun> {\n    let mut result = vec![];\n\n    let mut cursor = start_offset;\n    for run in runs {\n        let mut run_start = cursor;\n        let run_end = cursor + run.len;\n\n        for (bg_range, bg_color) in bg_segments {\n            if run_end <= bg_range.start || run_start >= bg_range.end {\n                continue;\n            }\n\n            // Overlap exists\n            if run_start < bg_range.start {\n                // Add the part before the background range\n                result.push(TextRun {\n                    len: bg_range.start - run_start,\n                    ..run.clone()\n                });\n            }\n\n            // Add the overlapping part with background color\n            let overlap_start = run_start.max(bg_range.start);\n            let overlap_end = run_end.min(bg_range.end);\n            let text_color = if bg_color.l >= 0.5 {\n                gpui::black()\n            } else {\n                gpui::white()\n            };\n\n            let run_len = overlap_end.saturating_sub(overlap_start);\n            if run_len > 0 {\n                result.push(TextRun {\n                    len: run_len,\n                    color: text_color,\n                    ..run.clone()\n                });\n\n                cursor = bg_range.end;\n                run_start = cursor;\n            }\n        }\n\n        if run_end > cursor {\n            // Add the part after the background range\n            result.push(TextRun {\n                len: run_end - cursor,\n                ..run.clone()\n            });\n        }\n\n        cursor = run_end;\n    }\n\n    result\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_runs_for_range() {\n        let run = TextRun {\n            len: 0,\n            font: gpui::font(\".SystemUIFont\"),\n            color: gpui::black(),\n            background_color: None,\n            underline: None,\n            strikethrough: None,\n        };\n\n        // use hello this-is-test\n        let runs = vec![\n            // use\n            TextRun {\n                len: 3,\n                ..run.clone()\n            },\n            // \\s\n            TextRun {\n                len: 1,\n                ..run.clone()\n            },\n            // hello\n            TextRun {\n                len: 5,\n                ..run.clone()\n            },\n            // \\s\n            TextRun {\n                len: 1,\n                ..run.clone()\n            },\n            // this-is-test\n            TextRun {\n                len: 12,\n                ..run.clone()\n            },\n        ];\n\n        #[track_caller]\n        fn assert_runs(actual: Vec<TextRun>, expected: &[usize]) {\n            let left = actual.iter().map(|run| run.len).collect::<Vec<_>>();\n            assert_eq!(left, expected);\n        }\n\n        assert_runs(runs_for_range(&runs, 0, &(0..0)), &[]);\n        assert_runs(runs_for_range(&runs, 0, &(0..100)), &[3, 1, 5, 1, 12]);\n\n        assert_runs(runs_for_range(&runs, 0, &(0..6)), &[3, 1, 2]);\n        assert_runs(runs_for_range(&runs, 0, &(1..6)), &[2, 1, 2]);\n        assert_runs(runs_for_range(&runs, 0, &(3..10)), &[1, 5, 1]);\n        assert_runs(runs_for_range(&runs, 0, &(5..8)), &[3]);\n        assert_runs(runs_for_range(&runs, 3, &(0..3)), &[1, 2]);\n        assert_runs(runs_for_range(&runs, 3, &(2..10)), &[4, 1, 3]);\n        assert_runs(runs_for_range(&runs, 9, &(0..8)), &[1, 7]);\n    }\n\n    #[test]\n    fn test_split_runs_by_bg_segments() {\n        let run = TextRun {\n            len: 0,\n            font: gpui::font(\".SystemUIFont\"),\n            color: gpui::blue(),\n            background_color: None,\n            underline: None,\n            strikethrough: None,\n        };\n\n        let runs = vec![\n            TextRun {\n                len: 5,\n                ..run.clone()\n            },\n            TextRun {\n                len: 7,\n                ..run.clone()\n            },\n            TextRun {\n                len: 24,\n                ..run.clone()\n            },\n        ];\n\n        let bg_segments = vec![(8..12, gpui::red()), (12..18, gpui::blue())];\n        let result = split_runs_by_bg_segments(5, &runs, &bg_segments);\n        assert_eq!(\n            result.iter().map(|run| run.len).collect::<Vec<_>>(),\n            vec![3, 2, 2, 5, 1, 23]\n        );\n        assert_eq!(result[0].color, gpui::blue());\n        assert_eq!(result[1].color, gpui::black());\n        assert_eq!(result[2].color, gpui::black());\n        assert_eq!(result[3].color, gpui::black());\n        assert_eq!(result[4].color, gpui::black());\n        assert_eq!(result[5].color, gpui::blue());\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/indent.rs",
    "content": "use gpui::{\n    Bounds, Context, EntityInputHandler as _, Hsla, Path, PathBuilder, Pixels, SharedString,\n    TextRun, TextStyle, Window, point, px,\n};\nuse ropey::RopeSlice;\n\nuse crate::{\n    RopeExt,\n    input::{\n        Indent, IndentInline, InputState, LastLayout, Outdent, OutdentInline, element::TextElement,\n        mode::InputMode,\n    },\n};\n\n#[derive(Debug, Copy, Clone)]\npub struct TabSize {\n    /// Default is 2\n    pub tab_size: usize,\n    /// Set true to use `\\t` as tab indent, default is false\n    pub hard_tabs: bool,\n}\n\nimpl Default for TabSize {\n    fn default() -> Self {\n        Self {\n            tab_size: 2,\n            hard_tabs: false,\n        }\n    }\n}\n\nimpl TabSize {\n    pub(super) fn to_string(&self) -> SharedString {\n        if self.hard_tabs {\n            \"\\t\".into()\n        } else {\n            \" \".repeat(self.tab_size).into()\n        }\n    }\n\n    /// Count the indent size of the line in spaces.\n    pub fn indent_count(&self, line: &RopeSlice) -> usize {\n        let mut count = 0;\n        for ch in line.chars() {\n            match ch {\n                '\\t' => count += self.tab_size,\n                ' ' => count += 1,\n                _ => break,\n            }\n        }\n\n        count\n    }\n}\n\nimpl InputMode {\n    #[inline]\n    pub(super) fn is_indentable(&self) -> bool {\n        match self {\n            InputMode::PlainText { multi_line, .. } | InputMode::CodeEditor { multi_line, .. } => {\n                *multi_line\n            }\n            _ => false,\n        }\n    }\n\n    #[inline]\n    pub(super) fn has_indent_guides(&self) -> bool {\n        match self {\n            InputMode::CodeEditor {\n                indent_guides,\n                multi_line,\n                ..\n            } => *indent_guides && *multi_line,\n            _ => false,\n        }\n    }\n\n    #[inline]\n    pub(super) fn tab_size(&self) -> TabSize {\n        match self {\n            InputMode::PlainText { tab, .. } => *tab,\n            InputMode::CodeEditor { tab, .. } => *tab,\n            _ => TabSize::default(),\n        }\n    }\n}\n\nimpl TextElement {\n    /// Measure the indent width in pixels for given column count.\n    fn measure_indent_width(&self, style: &TextStyle, column: usize, window: &Window) -> Pixels {\n        let font_size = style.font_size.to_pixels(window.rem_size());\n        let layout = window.text_system().shape_line(\n            SharedString::from(\" \".repeat(column)),\n            font_size,\n            &[TextRun {\n                len: column,\n                font: style.font(),\n                color: Hsla::default(),\n                background_color: None,\n                strikethrough: None,\n                underline: None,\n            }],\n            None,\n        );\n\n        layout.width\n    }\n\n    pub(super) fn layout_indent_guides(\n        &self,\n        state: &InputState,\n        bounds: &Bounds<Pixels>,\n        last_layout: &LastLayout,\n        text_style: &TextStyle,\n        window: &mut Window,\n    ) -> Option<Path<Pixels>> {\n        if !state.mode.has_indent_guides() {\n            return None;\n        }\n\n        let indent_width =\n            self.measure_indent_width(text_style, state.mode.tab_size().tab_size, window);\n\n        let tab_size = state.mode.tab_size();\n        let line_height = last_layout.line_height;\n        let mut builder = PathBuilder::stroke(px(1.));\n        let mut offset_y = last_layout.visible_top;\n        let mut last_indents = vec![];\n\n        for (&buffer_line, line_layout) in last_layout\n            .visible_buffer_lines\n            .iter()\n            .zip(last_layout.lines.iter())\n        {\n            let line = state.text.slice_line(buffer_line);\n            let mut current_indents = vec![];\n            if line.len() > 0 {\n                let indent_count = tab_size.indent_count(&line);\n                for offset in (0..indent_count).step_by(tab_size.tab_size) {\n                    let x = if indent_count > 0 {\n                        indent_width * offset as f32 / tab_size.tab_size as f32\n                    } else {\n                        px(0.)\n                    };\n\n                    let pos = point(x + last_layout.line_number_width, offset_y);\n\n                    builder.move_to(pos);\n                    builder.line_to(point(pos.x, pos.y + line_height));\n                    current_indents.push(pos.x);\n                }\n            } else if last_indents.len() > 0 {\n                for x in &last_indents {\n                    let pos = point(*x, offset_y);\n                    builder.move_to(pos);\n                    builder.line_to(point(pos.x, pos.y + line_height));\n                }\n                current_indents = last_indents.clone();\n            }\n\n            offset_y += line_layout.wrapped_lines.len() * line_height;\n            last_indents = current_indents;\n        }\n\n        builder.translate(bounds.origin);\n        let path = builder.build().unwrap();\n        Some(path)\n    }\n}\n\nimpl InputState {\n    /// Set whether to show indent guides in code editor mode, default is true.\n    ///\n    /// Only for [`InputMode::CodeEditor`] mode.\n    pub fn indent_guides(mut self, indent_guides: bool) -> Self {\n        debug_assert!(self.mode.is_code_editor() && self.mode.is_multi_line());\n        if let InputMode::CodeEditor {\n            indent_guides: l, ..\n        } = &mut self.mode\n        {\n            *l = indent_guides;\n        }\n        self\n    }\n\n    /// Set indent guides in code editor mode.\n    ///\n    /// Only for [`InputMode::CodeEditor`] mode.\n    pub fn set_indent_guides(\n        &mut self,\n        indent_guides: bool,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        debug_assert!(self.mode.is_code_editor());\n        if let InputMode::CodeEditor {\n            indent_guides: l, ..\n        } = &mut self.mode\n        {\n            *l = indent_guides;\n        }\n        cx.notify();\n    }\n\n    /// Set the tab size for the input.\n    ///\n    /// Only for [`InputMode::PlainText`] and [`InputMode::CodeEditor`] mode with multi_line.\n    pub fn tab_size(mut self, tab: TabSize) -> Self {\n        debug_assert!(self.mode.is_multi_line() || self.mode.is_code_editor());\n        match &mut self.mode {\n            InputMode::PlainText { tab: t, .. } => *t = tab,\n            InputMode::CodeEditor { tab: t, .. } => *t = tab,\n            _ => {}\n        }\n        self\n    }\n\n    pub(super) fn indent_inline(\n        &mut self,\n        _: &IndentInline,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        // First, try to accept inline completion if present\n        if self.accept_inline_completion(window, cx) {\n            return;\n        }\n        self.indent(false, window, cx);\n    }\n\n    pub(super) fn indent_block(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {\n        self.indent(true, window, cx);\n    }\n\n    pub(super) fn outdent_inline(\n        &mut self,\n        _: &OutdentInline,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.outdent(false, window, cx);\n    }\n\n    pub(super) fn outdent_block(\n        &mut self,\n        _: &Outdent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.outdent(true, window, cx);\n    }\n\n    pub(super) fn indent(&mut self, block: bool, window: &mut Window, cx: &mut Context<Self>) {\n        if !self.mode.is_indentable() {\n            cx.propagate();\n            return;\n        };\n\n        let tab_indent = self.mode.tab_size().to_string();\n        let selected_range = self.selected_range;\n        let mut added_len = 0;\n        let is_selected = !self.selected_range.is_empty();\n\n        if is_selected || block {\n            let start_offset = self.start_of_line_of_selection(window, cx);\n            let mut offset = start_offset;\n\n            let selected_text = self\n                .text_for_range(\n                    self.range_to_utf16(&(offset..selected_range.end)),\n                    &mut None,\n                    window,\n                    cx,\n                )\n                .unwrap_or(\"\".into());\n\n            for line in selected_text.split('\\n') {\n                self.replace_text_in_range_silent(\n                    Some(self.range_to_utf16(&(offset..offset))),\n                    &tab_indent,\n                    window,\n                    cx,\n                );\n                added_len += tab_indent.len();\n                // +1 for \"\\n\", the `\\r` is included in the `line`.\n                offset += line.len() + tab_indent.len() + 1;\n            }\n\n            if is_selected {\n                self.selected_range = (start_offset..selected_range.end + added_len).into();\n            } else {\n                self.selected_range =\n                    (selected_range.start + added_len..selected_range.end + added_len).into();\n            }\n        } else {\n            // Selected none\n            let offset = self.selected_range.start;\n            self.replace_text_in_range_silent(\n                Some(self.range_to_utf16(&(offset..offset))),\n                &tab_indent,\n                window,\n                cx,\n            );\n            added_len = tab_indent.len();\n\n            self.selected_range =\n                (selected_range.start + added_len..selected_range.end + added_len).into();\n        }\n    }\n\n    pub(super) fn outdent(&mut self, block: bool, window: &mut Window, cx: &mut Context<Self>) {\n        if !self.mode.is_indentable() {\n            cx.propagate();\n            return;\n        };\n\n        let tab_indent = self.mode.tab_size().to_string();\n        let selected_range = self.selected_range;\n        let mut removed_len = 0;\n        let is_selected = !self.selected_range.is_empty();\n\n        if is_selected || block {\n            let start_offset = self.start_of_line_of_selection(window, cx);\n            let mut offset = start_offset;\n\n            let selected_text = self\n                .text_for_range(\n                    self.range_to_utf16(&(offset..selected_range.end)),\n                    &mut None,\n                    window,\n                    cx,\n                )\n                .unwrap_or(\"\".into());\n\n            for line in selected_text.split('\\n') {\n                if line.starts_with(tab_indent.as_ref()) {\n                    self.replace_text_in_range_silent(\n                        Some(self.range_to_utf16(&(offset..offset + tab_indent.len()))),\n                        \"\",\n                        window,\n                        cx,\n                    );\n                    removed_len += tab_indent.len();\n\n                    // +1 for \"\\n\"\n                    offset += line.len().saturating_sub(tab_indent.len()) + 1;\n                } else {\n                    offset += line.len() + 1;\n                }\n            }\n\n            if is_selected {\n                self.selected_range =\n                    (start_offset..selected_range.end.saturating_sub(removed_len)).into();\n            } else {\n                self.selected_range = (selected_range.start.saturating_sub(removed_len)\n                    ..selected_range.end.saturating_sub(removed_len))\n                    .into();\n            }\n        } else {\n            // Selected none\n            let start_offset = self.selected_range.start;\n            let offset = self.start_of_line_of_selection(window, cx);\n            let offset = self.offset_from_utf16(self.offset_to_utf16(offset));\n            // FIXME: To improve performance\n            if self\n                .text\n                .slice(offset..self.text.len())\n                .to_string()\n                .starts_with(tab_indent.as_ref())\n            {\n                self.replace_text_in_range_silent(\n                    Some(self.range_to_utf16(&(offset..offset + tab_indent.len()))),\n                    \"\",\n                    window,\n                    cx,\n                );\n                removed_len = tab_indent.len();\n                let new_offset = start_offset.saturating_sub(removed_len);\n                self.selected_range = (new_offset..new_offset).into();\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use ropey::RopeSlice;\n\n    use super::TabSize;\n\n    #[test]\n    fn test_tab_size() {\n        let tab = TabSize {\n            tab_size: 2,\n            hard_tabs: false,\n        };\n        assert_eq!(tab.to_string(), \"  \");\n        let tab = TabSize {\n            tab_size: 4,\n            hard_tabs: false,\n        };\n        assert_eq!(tab.to_string(), \"    \");\n\n        let tab = TabSize {\n            tab_size: 2,\n            hard_tabs: true,\n        };\n        assert_eq!(tab.to_string(), \"\\t\");\n        let tab = TabSize {\n            tab_size: 4,\n            hard_tabs: true,\n        };\n        assert_eq!(tab.to_string(), \"\\t\");\n    }\n\n    #[test]\n    fn test_tab_size_indent_count() {\n        let tab = TabSize {\n            tab_size: 4,\n            hard_tabs: false,\n        };\n        assert_eq!(tab.indent_count(&RopeSlice::from(\"abc\")), 0);\n        assert_eq!(tab.indent_count(&RopeSlice::from(\"  abc\")), 2);\n        assert_eq!(tab.indent_count(&RopeSlice::from(\"    abc\")), 4);\n        assert_eq!(tab.indent_count(&RopeSlice::from(\"\\tabc\")), 4);\n        assert_eq!(tab.indent_count(&RopeSlice::from(\"  \\tabc\")), 6);\n        assert_eq!(tab.indent_count(&RopeSlice::from(\" \\t abc  \")), 6);\n        assert_eq!(tab.indent_count(&RopeSlice::from(\"abc\")), 0);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/input.rs",
    "content": "use gpui::prelude::FluentBuilder as _;\nuse gpui::{\n    AnyElement, App, DefiniteLength, Edges, EdgesRefinement, Entity, Hsla, InteractiveElement as _,\n    IntoElement, IsZero, MouseButton, ParentElement as _, Rems, RenderOnce, StyleRefinement,\n    Styled, TextAlign, Window, div, px, relative,\n};\n\nuse crate::button::{Button, ButtonVariants as _};\nuse crate::input::clear_button;\nuse crate::input::element::{LINE_NUMBER_RIGHT_MARGIN, RIGHT_MARGIN};\nuse crate::scroll::Scrollbar;\nuse crate::spinner::Spinner;\nuse crate::{ActiveTheme, Colorize, v_flex};\nuse crate::{IconName, Size};\nuse crate::{Selectable, StyledExt, h_flex};\nuse crate::{Sizable, StyleSized};\n\nuse super::InputState;\n\n/// Returns `(background, foreground)` colors for input-like components.\npub(crate) fn input_style(disabled: bool, cx: &App) -> (Hsla, Hsla) {\n    if disabled {\n        (\n            cx.theme().input.mix_oklab(cx.theme().transparent, 0.8),\n            cx.theme().muted_foreground,\n        )\n    } else {\n        (cx.theme().input_background(), cx.theme().foreground)\n    }\n}\n\n/// A text input element bind to an [`InputState`].\n#[derive(IntoElement)]\npub struct Input {\n    state: Entity<InputState>,\n    style: StyleRefinement,\n    size: Size,\n    prefix: Option<AnyElement>,\n    suffix: Option<AnyElement>,\n    height: Option<DefiniteLength>,\n    appearance: bool,\n    cleanable: bool,\n    mask_toggle: bool,\n    disabled: bool,\n    bordered: bool,\n    focus_bordered: bool,\n    tab_index: isize,\n    selected: bool,\n}\n\nimpl Sizable for Input {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Selectable for Input {\n    fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        self.selected\n    }\n}\n\nimpl Input {\n    /// Create a new [`Input`] element bind to the [`InputState`].\n    pub fn new(state: &Entity<InputState>) -> Self {\n        Self {\n            state: state.clone(),\n            size: Size::default(),\n            style: StyleRefinement::default(),\n            prefix: None,\n            suffix: None,\n            height: None,\n            appearance: true,\n            cleanable: false,\n            mask_toggle: false,\n            disabled: false,\n            bordered: true,\n            focus_bordered: true,\n            tab_index: 0,\n            selected: false,\n        }\n    }\n\n    pub fn prefix(mut self, prefix: impl IntoElement) -> Self {\n        self.prefix = Some(prefix.into_any_element());\n        self\n    }\n\n    pub fn suffix(mut self, suffix: impl IntoElement) -> Self {\n        self.suffix = Some(suffix.into_any_element());\n        self\n    }\n\n    /// Set full height of the input (Multi-line only).\n    pub fn h_full(mut self) -> Self {\n        self.height = Some(relative(1.));\n        self\n    }\n\n    /// Set height of the input (Multi-line only).\n    pub fn h(mut self, height: impl Into<DefiniteLength>) -> Self {\n        self.height = Some(height.into());\n        self\n    }\n\n    /// Set the appearance of the input field, if false the input field will no border, background.\n    pub fn appearance(mut self, appearance: bool) -> Self {\n        self.appearance = appearance;\n        self\n    }\n\n    /// Set the bordered for the input, default: true\n    pub fn bordered(mut self, bordered: bool) -> Self {\n        self.bordered = bordered;\n        self\n    }\n\n    /// Set focus border for the input, default is true.\n    pub fn focus_bordered(mut self, bordered: bool) -> Self {\n        self.focus_bordered = bordered;\n        self\n    }\n\n    /// Set whether to show the clear button when the input field is not empty, default is false.\n    pub fn cleanable(mut self, cleanable: bool) -> Self {\n        self.cleanable = cleanable;\n        self\n    }\n\n    /// Set to enable toggle button for password mask state.\n    pub fn mask_toggle(mut self) -> Self {\n        self.mask_toggle = true;\n        self\n    }\n\n    /// Set to disable the input field.\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    /// Set the tab index for the input, default is 0.\n    pub fn tab_index(mut self, index: isize) -> Self {\n        self.tab_index = index;\n        self\n    }\n\n    fn render_toggle_mask_button(state: Entity<InputState>) -> impl IntoElement {\n        Button::new(\"toggle-mask\")\n            .icon(IconName::Eye)\n            .xsmall()\n            .ghost()\n            .tab_stop(false)\n            .on_mouse_down(MouseButton::Left, {\n                let state = state.clone();\n                move |_, window, cx| {\n                    state.update(cx, |state, cx| {\n                        state.set_masked(false, window, cx);\n                    })\n                }\n            })\n            .on_mouse_up(MouseButton::Left, {\n                let state = state.clone();\n                move |_, window, cx| {\n                    state.update(cx, |state, cx| {\n                        state.set_masked(true, window, cx);\n                    })\n                }\n            })\n    }\n\n    /// This method must after the refine_style.\n    fn render_editor(\n        paddings: EdgesRefinement<DefiniteLength>,\n        input_state: &Entity<InputState>,\n        state: &InputState,\n        window: &Window,\n        _cx: &App,\n    ) -> impl IntoElement {\n        let base_size = window.text_style().font_size;\n        let rem_size = window.rem_size();\n\n        let paddings = Edges {\n            left: paddings\n                .left\n                .map(|v| v.to_pixels(base_size, rem_size))\n                .unwrap_or(px(0.)),\n            right: paddings\n                .right\n                .map(|v| v.to_pixels(base_size, rem_size))\n                .unwrap_or(px(0.)),\n            top: paddings\n                .top\n                .map(|v| v.to_pixels(base_size, rem_size))\n                .unwrap_or(px(0.)),\n            bottom: paddings\n                .bottom\n                .map(|v| v.to_pixels(base_size, rem_size))\n                .unwrap_or(px(0.)),\n        };\n\n        v_flex()\n            .size_full()\n            .children(state.search_panel.clone())\n            .child(div().flex_1().child(input_state.clone()).map(|this| {\n                if let Some(last_layout) = state.last_layout.as_ref() {\n                    let left = if last_layout.line_number_width.is_zero() {\n                        px(0.)\n                    } else {\n                        // Align left edge to the Line number.\n                        paddings.left + last_layout.line_number_width - LINE_NUMBER_RIGHT_MARGIN\n                    };\n\n                    let scroll_size = gpui::Size {\n                        width: state.scroll_size.width - left + paddings.right + RIGHT_MARGIN,\n                        height: state.scroll_size.height,\n                    };\n\n                    let scrollbar = if !state.soft_wrap {\n                        Scrollbar::new(&state.scroll_handle)\n                    } else {\n                        Scrollbar::vertical(&state.scroll_handle)\n                    };\n\n                    this.relative().child(\n                        div()\n                            .absolute()\n                            .top(-paddings.top)\n                            .left(left)\n                            .right(-paddings.right)\n                            .bottom(-paddings.bottom)\n                            .child(scrollbar.scroll_size(scroll_size)),\n                    )\n                } else {\n                    this\n                }\n            }))\n    }\n}\n\nimpl Styled for Input {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Input {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        const LINE_HEIGHT: Rems = Rems(1.25);\n        let text_align = self.style.text.text_align.unwrap_or(TextAlign::Left);\n\n        self.state.update(cx, |state, _| {\n            state.disabled = self.disabled;\n            state.size = self.size;\n            // Only for single line mode\n            if state.mode.is_single_line() {\n                state.text_align = text_align;\n            }\n        });\n\n        let state = self.state.read(cx);\n        let focused = state.focus_handle.is_focused(window) && !state.disabled;\n        let gap_x = match self.size {\n            Size::Small => px(4.),\n            Size::Large => px(8.),\n            _ => px(6.),\n        };\n\n        let (bg, fg) = input_style(state.disabled, cx);\n        let bg = if state.mode.is_code_editor() {\n            cx.theme().editor_background()\n        } else {\n            bg\n        };\n\n        let prefix = self.prefix;\n        let suffix = self.suffix;\n        let show_clear_button = self.cleanable\n            && !state.disabled\n            && !state.loading\n            && state.text.len() > 0\n            && state.mode.is_single_line();\n        let has_suffix = suffix.is_some() || state.loading || self.mask_toggle || show_clear_button;\n\n        div()\n            .id((\"input\", self.state.entity_id()))\n            .flex()\n            .key_context(crate::input::CONTEXT)\n            .track_focus(&state.focus_handle.clone())\n            .tab_index(self.tab_index)\n            .when(!state.disabled, |this| {\n                this.on_action(window.listener_for(&self.state, InputState::backspace))\n                    .on_action(window.listener_for(&self.state, InputState::delete))\n                    .on_action(\n                        window.listener_for(&self.state, InputState::delete_to_beginning_of_line),\n                    )\n                    .on_action(window.listener_for(&self.state, InputState::delete_to_end_of_line))\n                    .on_action(window.listener_for(&self.state, InputState::delete_previous_word))\n                    .on_action(window.listener_for(&self.state, InputState::delete_next_word))\n                    .on_action(window.listener_for(&self.state, InputState::enter))\n                    .on_action(window.listener_for(&self.state, InputState::escape))\n                    .on_action(window.listener_for(&self.state, InputState::paste))\n                    .on_action(window.listener_for(&self.state, InputState::cut))\n                    .on_action(window.listener_for(&self.state, InputState::undo))\n                    .on_action(window.listener_for(&self.state, InputState::redo))\n                    .when(state.mode.is_multi_line(), |this| {\n                        this.on_action(window.listener_for(&self.state, InputState::indent_inline))\n                            .on_action(window.listener_for(&self.state, InputState::outdent_inline))\n                            .on_action(window.listener_for(&self.state, InputState::indent_block))\n                            .on_action(window.listener_for(&self.state, InputState::outdent_block))\n                    })\n                    .on_action(\n                        window.listener_for(&self.state, InputState::on_action_toggle_code_actions),\n                    )\n            })\n            .on_action(window.listener_for(&self.state, InputState::left))\n            .on_action(window.listener_for(&self.state, InputState::right))\n            .on_action(window.listener_for(&self.state, InputState::select_left))\n            .on_action(window.listener_for(&self.state, InputState::select_right))\n            .when(state.mode.is_multi_line(), |this| {\n                let result = this\n                    .on_action(window.listener_for(&self.state, InputState::up))\n                    .on_action(window.listener_for(&self.state, InputState::down))\n                    .on_action(window.listener_for(&self.state, InputState::select_up))\n                    .on_action(window.listener_for(&self.state, InputState::select_down))\n                    .on_action(window.listener_for(&self.state, InputState::page_up))\n                    .on_action(window.listener_for(&self.state, InputState::page_down));\n\n                let result = result.on_action(\n                    window.listener_for(&self.state, InputState::on_action_go_to_definition),\n                );\n\n                result\n            })\n            .on_action(window.listener_for(&self.state, InputState::select_all))\n            .on_action(window.listener_for(&self.state, InputState::select_to_start_of_line))\n            .on_action(window.listener_for(&self.state, InputState::select_to_end_of_line))\n            .on_action(window.listener_for(&self.state, InputState::select_to_previous_word))\n            .on_action(window.listener_for(&self.state, InputState::select_to_next_word))\n            .on_action(window.listener_for(&self.state, InputState::home))\n            .on_action(window.listener_for(&self.state, InputState::end))\n            .on_action(window.listener_for(&self.state, InputState::move_to_start))\n            .on_action(window.listener_for(&self.state, InputState::move_to_end))\n            .on_action(window.listener_for(&self.state, InputState::move_to_previous_word))\n            .on_action(window.listener_for(&self.state, InputState::move_to_next_word))\n            .on_action(window.listener_for(&self.state, InputState::select_to_start))\n            .on_action(window.listener_for(&self.state, InputState::select_to_end))\n            .on_action(window.listener_for(&self.state, InputState::show_character_palette))\n            .on_action(window.listener_for(&self.state, InputState::copy))\n            .on_action(window.listener_for(&self.state, InputState::on_action_search))\n            .on_key_down(window.listener_for(&self.state, InputState::on_key_down))\n            .on_mouse_down(\n                MouseButton::Left,\n                window.listener_for(&self.state, InputState::on_mouse_down),\n            )\n            .on_mouse_down(\n                MouseButton::Right,\n                window.listener_for(&self.state, InputState::on_mouse_down),\n            )\n            .on_mouse_up(\n                MouseButton::Left,\n                window.listener_for(&self.state, InputState::on_mouse_up),\n            )\n            .on_mouse_up(\n                MouseButton::Right,\n                window.listener_for(&self.state, InputState::on_mouse_up),\n            )\n            .on_mouse_move(window.listener_for(&self.state, InputState::on_mouse_move))\n            .on_scroll_wheel(window.listener_for(&self.state, InputState::on_scroll_wheel))\n            .size_full()\n            .line_height(LINE_HEIGHT)\n            .input_px(self.size)\n            .input_py(self.size)\n            .input_h(self.size)\n            .input_text_size(self.size)\n            .when(!self.disabled, |this| this.cursor_text())\n            .items_center()\n            .when(state.mode.is_multi_line(), |this| {\n                this.h_auto()\n                    .when_some(self.height, |this, height| this.h(height))\n            })\n            .when(self.appearance, |this| {\n                this.bg(bg)\n                    .text_color(fg)\n                    .when(self.disabled, |this| this.opacity(0.5))\n                    .rounded(cx.theme().radius)\n                    .when(self.bordered, |this| {\n                        this.border_color(cx.theme().input)\n                            .border_1()\n                            .when(cx.theme().shadow, |this| this.shadow_xs())\n                            .when(focused && self.focus_bordered, |this| {\n                                this.focused_border(cx)\n                            })\n                    })\n            })\n            .items_center()\n            .gap(gap_x)\n            .refine_style(&self.style)\n            .children(prefix)\n            .when(state.mode.is_multi_line(), |mut this| {\n                let paddings = this.style().padding.clone();\n                this.child(Self::render_editor(\n                    paddings,\n                    &self.state,\n                    &state,\n                    window,\n                    cx,\n                ))\n            })\n            .when(!state.mode.is_multi_line(), |this| {\n                this.child(self.state.clone())\n            })\n            .when(has_suffix, |this| {\n                this.pr(self.size.input_px()).child(\n                    h_flex()\n                        .id(\"suffix\")\n                        .gap(gap_x)\n                        .items_center()\n                        .when(state.loading, |this| {\n                            this.child(Spinner::new().color(cx.theme().muted_foreground))\n                        })\n                        .when(self.mask_toggle, |this| {\n                            this.child(Self::render_toggle_mask_button(self.state.clone()))\n                        })\n                        .when(show_clear_button, |this| {\n                            this.child(clear_button(cx).on_click({\n                                let state = self.state.clone();\n                                move |_, window, cx| {\n                                    state.update(cx, |state, cx| {\n                                        state.clean(window, cx);\n                                        state.focus(window, cx);\n                                    })\n                                }\n                            }))\n                        })\n                        .children(suffix),\n                )\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/lsp/code_actions.rs",
    "content": "use anyhow::Result;\nuse gpui::{App, Context, Entity, SharedString, Task, Window};\nuse lsp_types::CodeAction;\nuse std::ops::Range;\n\nuse crate::input::{\n    popovers::{CodeActionItem, CodeActionMenu, ContextMenu},\n    InputState, ToggleCodeActions,\n};\n\npub trait CodeActionProvider {\n    /// The id for this CodeAction.\n    fn id(&self) -> SharedString;\n\n    /// Fetches code actions for the specified range.\n    ///\n    /// textDocument/codeAction\n    ///\n    /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction\n    fn code_actions(\n        &self,\n        state: Entity<InputState>,\n        range: Range<usize>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Task<Result<Vec<CodeAction>>>;\n\n    /// Performs the specified code action.\n    fn perform_code_action(\n        &self,\n        state: Entity<InputState>,\n        action: CodeAction,\n        push_to_history: bool,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Task<Result<()>>;\n}\n\nimpl InputState {\n    pub(crate) fn on_action_toggle_code_actions(\n        &mut self,\n        _: &ToggleCodeActions,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.handle_code_action_trigger(window, cx)\n    }\n\n    /// Show code actions for the cursor.\n    pub(crate) fn handle_code_action_trigger(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let providers = self.lsp.code_action_providers.clone();\n        let menu = match self.context_menu.as_ref() {\n            Some(ContextMenu::CodeAction(menu)) => Some(menu),\n            _ => None,\n        };\n\n        let menu = match menu {\n            Some(menu) => menu.clone(),\n            None => {\n                let menu = CodeActionMenu::new(cx.entity(), window, cx);\n                self.context_menu = Some(ContextMenu::CodeAction(menu.clone()));\n                menu\n            }\n        };\n\n        let range = self.selected_range.start..self.selected_range.end;\n\n        let state = cx.entity();\n        self._context_menu_task = cx.spawn_in(window, async move |editor, cx| {\n            let mut provider_responses = vec![];\n            _ = cx.update(|window, cx| {\n                for provider in providers {\n                    let task = provider.code_actions(state.clone(), range.clone(), window, cx);\n                    provider_responses.push((provider.id(), task));\n                }\n            });\n\n            let mut code_actions: Vec<CodeActionItem> = vec![];\n            for (provider_id, provider_responses) in provider_responses {\n                if let Some(responses) = provider_responses.await.ok() {\n                    code_actions.extend(responses.into_iter().map(|action| CodeActionItem {\n                        provider_id: provider_id.clone(),\n                        action,\n                    }))\n                }\n            }\n\n            if code_actions.is_empty() {\n                _ = menu.update(cx, |menu, cx| {\n                    menu.hide(cx);\n                    cx.notify();\n                });\n\n                return Ok(());\n            }\n            editor\n                .update_in(cx, |editor, window, cx| {\n                    if !editor.focus_handle.is_focused(window) {\n                        return;\n                    }\n\n                    _ = menu.update(cx, |menu, cx| {\n                        menu.show(editor.cursor(), code_actions, window, cx);\n                    });\n\n                    cx.notify();\n                })\n                .ok();\n\n            Ok(())\n        });\n    }\n\n    pub(crate) fn perform_code_action(\n        &mut self,\n        item: &CodeActionItem,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let providers = self.lsp.code_action_providers.clone();\n        let Some(provider) = providers\n            .iter()\n            .find(|provider| provider.id() == item.provider_id)\n        else {\n            return;\n        };\n\n        let state = cx.entity();\n        let task = provider.perform_code_action(state, item.action.clone(), true, window, cx);\n\n        cx.spawn_in(window, async move |_, _| {\n            let _ = task.await;\n        })\n        .detach();\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/lsp/completions.rs",
    "content": "use anyhow::Result;\nuse gpui::{Context, EntityInputHandler, Task, Window};\nuse lsp_types::{\n    CompletionContext, CompletionItem, CompletionResponse, InlineCompletionContext,\n    InlineCompletionItem, InlineCompletionResponse, InlineCompletionTriggerKind,\n    request::Completion,\n};\nuse ropey::Rope;\nuse std::{cell::RefCell, ops::Range, rc::Rc, time::Duration};\n\nuse crate::input::{\n    InputState,\n    popovers::{CompletionMenu, ContextMenu},\n};\n\n/// Default debounce duration for inline completions.\nconst DEFAULT_INLINE_COMPLETION_DEBOUNCE: Duration = Duration::from_millis(300);\n\n/// A trait for providing code completions based on the current input state and context.\npub trait CompletionProvider {\n    /// Fetches completions based on the given byte offset.\n    ///\n    /// - The `offset` is in bytes of current cursor.\n    ///\n    /// textDocument/completion\n    ///\n    /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion\n    fn completions(\n        &self,\n        text: &Rope,\n        offset: usize,\n        trigger: CompletionContext,\n        window: &mut Window,\n        cx: &mut Context<InputState>,\n    ) -> Task<Result<CompletionResponse>>;\n\n    /// Fetches an inline completion suggestion for the given position.\n    ///\n    /// This is called after a debounce period when the user stops typing.\n    /// The provider can analyze the text and cursor position to determine\n    /// what inline completion suggestion to show.\n    ///\n    ///\n    /// # Arguments\n    /// * `rope` - The current text content\n    /// * `offset` - The cursor position in bytes\n    ///\n    /// textDocument/inlineCompletion\n    ///\n    /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#textDocument_inlineCompletion\n    fn inline_completion(\n        &self,\n        _rope: &Rope,\n        _offset: usize,\n        _trigger: InlineCompletionContext,\n        _window: &mut Window,\n        _cx: &mut Context<InputState>,\n    ) -> Task<Result<InlineCompletionResponse>> {\n        Task::ready(Ok(InlineCompletionResponse::Array(vec![])))\n    }\n\n    /// Returns the debounce duration for inline completions.\n    ///\n    /// Default: 300ms\n    #[inline]\n    fn inline_completion_debounce(&self) -> Duration {\n        DEFAULT_INLINE_COMPLETION_DEBOUNCE\n    }\n\n    fn resolve_completions(\n        &self,\n        _completion_indices: Vec<usize>,\n        _completions: Rc<RefCell<Box<[Completion]>>>,\n        _: &mut Context<InputState>,\n    ) -> Task<Result<bool>> {\n        Task::ready(Ok(false))\n    }\n\n    /// Determines if the completion should be triggered based on the given byte offset.\n    ///\n    /// This is called on the main thread.\n    fn is_completion_trigger(\n        &self,\n        offset: usize,\n        new_text: &str,\n        cx: &mut Context<InputState>,\n    ) -> bool;\n}\n\npub(crate) struct InlineCompletion {\n    /// Completion item to display as an inline completion suggestion\n    pub(crate) item: Option<InlineCompletionItem>,\n    /// Task for debouncing inline completion requests\n    pub(crate) task: Task<Result<InlineCompletionResponse>>,\n}\n\nimpl Default for InlineCompletion {\n    fn default() -> Self {\n        Self {\n            item: None,\n            task: Task::ready(Ok(InlineCompletionResponse::Array(vec![]))),\n        }\n    }\n}\n\nimpl InputState {\n    pub(crate) fn handle_completion_trigger(\n        &mut self,\n        range: &Range<usize>,\n        new_text: &str,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if self.completion_inserting {\n            return;\n        }\n\n        let Some(provider) = self.lsp.completion_provider.clone() else {\n            return;\n        };\n\n        // Always schedule inline completion (debounced).\n        // It will check if menu is open before showing the suggestion.\n        self.schedule_inline_completion(window, cx);\n\n        let start = range.end;\n        let new_offset = self.cursor();\n\n        if !provider.is_completion_trigger(start, new_text, cx) {\n            return;\n        }\n\n        let menu = match self.context_menu.as_ref() {\n            Some(ContextMenu::Completion(menu)) => Some(menu),\n            _ => None,\n        };\n\n        // To create or get the existing completion menu.\n        let menu = match menu {\n            Some(menu) => menu.clone(),\n            None => {\n                let menu = CompletionMenu::new(cx.entity(), window, cx);\n                self.context_menu = Some(ContextMenu::Completion(menu.clone()));\n                menu\n            }\n        };\n\n        let start_offset = menu.read(cx).trigger_start_offset.unwrap_or(start);\n        if new_offset < start_offset {\n            return;\n        }\n\n        let query = self\n            .text_for_range(\n                self.range_to_utf16(&(start_offset..new_offset)),\n                &mut None,\n                window,\n                cx,\n            )\n            .map(|s| s.trim().to_string())\n            .unwrap_or_default();\n        _ = menu.update(cx, |menu, _| {\n            menu.update_query(start_offset, query.clone());\n        });\n\n        let completion_context = CompletionContext {\n            trigger_kind: lsp_types::CompletionTriggerKind::TRIGGER_CHARACTER,\n            trigger_character: Some(query),\n        };\n\n        let provider_responses =\n            provider.completions(&self.text, new_offset, completion_context, window, cx);\n        self._context_menu_task = cx.spawn_in(window, async move |editor, cx| {\n            let mut completions: Vec<CompletionItem> = vec![];\n            if let Some(provider_responses) = provider_responses.await.ok() {\n                match provider_responses {\n                    CompletionResponse::Array(items) => completions.extend(items),\n                    CompletionResponse::List(list) => completions.extend(list.items),\n                }\n            }\n\n            if completions.is_empty() {\n                _ = menu.update(cx, |menu, cx| {\n                    menu.hide(cx);\n                    cx.notify();\n                });\n\n                return Ok(());\n            }\n\n            editor\n                .update_in(cx, |editor, window, cx| {\n                    if !editor.focus_handle.is_focused(window) {\n                        return;\n                    }\n\n                    _ = menu.update(cx, |menu, cx| {\n                        menu.show(new_offset, completions, window, cx);\n                    });\n\n                    cx.notify();\n                })\n                .ok();\n\n            Ok(())\n        });\n    }\n\n    /// Schedule an inline completion request after debouncing.\n    pub(crate) fn schedule_inline_completion(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        // Clear any existing inline completion on text change\n        self.clear_inline_completion(cx);\n\n        let Some(provider) = self.lsp.completion_provider.clone() else {\n            return;\n        };\n\n        let offset = self.cursor();\n        let text = self.text.clone();\n        let debounce = provider.inline_completion_debounce();\n        let background_executor = cx.background_executor().clone();\n\n        self.inline_completion.task = cx.spawn_in(window, async move |editor, cx| {\n            // Debounce: wait before fetching to avoid unnecessary requests while typing\n            background_executor.timer(debounce).await;\n\n            // Now fetch the inline completion after the debounce period\n            let task = editor.update_in(cx, |editor, window, cx| {\n                // Check if cursor has moved during debounce\n                if editor.cursor() != offset {\n                    return None;\n                }\n\n                // Don't fetch if completion menu is open\n                if editor.is_context_menu_open(cx) {\n                    return None;\n                }\n\n                let trigger = InlineCompletionContext {\n                    trigger_kind: InlineCompletionTriggerKind::Automatic,\n                    selected_completion_info: None,\n                };\n\n                Some(provider.inline_completion(&text, offset, trigger, window, cx))\n            })?;\n\n            let Some(task) = task else {\n                return Ok(InlineCompletionResponse::Array(vec![]));\n            };\n\n            let response = task.await?;\n\n            editor.update_in(cx, |editor, _window, cx| {\n                // Only apply if cursor still hasn't moved\n                if editor.cursor() != offset {\n                    return;\n                }\n\n                // Don't show if completion menu opened while we were fetching\n                if editor.is_context_menu_open(cx) {\n                    return;\n                }\n\n                if let Some(item) = match response.clone() {\n                    InlineCompletionResponse::Array(items) => items.into_iter().next(),\n                    InlineCompletionResponse::List(comp_list) => comp_list.items.into_iter().next(),\n                } {\n                    editor.inline_completion.item = Some(item);\n                    cx.notify();\n                }\n            })?;\n\n            Ok(response)\n        });\n    }\n\n    /// Check if an inline completion suggestion is currently displayed.\n    #[inline]\n    pub(crate) fn has_inline_completion(&self) -> bool {\n        self.inline_completion.item.is_some()\n    }\n\n    /// Clear the inline completion suggestion.\n    pub(crate) fn clear_inline_completion(&mut self, cx: &mut Context<Self>) {\n        self.inline_completion = InlineCompletion::default();\n        cx.notify();\n    }\n\n    /// Accept the inline completion, inserting it at the cursor position.\n    /// Returns true if a completion was accepted, false if there was none.\n    pub(crate) fn accept_inline_completion(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> bool {\n        let Some(completion_item) = self.inline_completion.item.take() else {\n            return false;\n        };\n\n        let cursor = self.cursor();\n        let range_utf16 = self.range_to_utf16(&(cursor..cursor));\n        let completion_text = completion_item.insert_text;\n        self.replace_text_in_range_silent(Some(range_utf16), &completion_text, window, cx);\n        true\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/lsp/definitions.rs",
    "content": "use anyhow::Result;\nuse gpui::{\n    App, Context, HighlightStyle, Hitbox, MouseDownEvent, Task, UnderlineStyle, Window, px,\n};\nuse ropey::Rope;\nuse std::{ops::Range, rc::Rc};\n\nuse crate::{\n    ActiveTheme,\n    input::{GoToDefinition, InputState, RopeExt, element::TextElement},\n};\n\n/// Definition provider\n///\n/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition\npub trait DefinitionProvider {\n    /// textDocument/definition\n    ///\n    /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition\n    fn definitions(\n        &self,\n        _text: &Rope,\n        _offset: usize,\n        _window: &mut Window,\n        _cx: &mut App,\n    ) -> Task<Result<Vec<lsp_types::LocationLink>>>;\n}\n\n#[derive(Clone, Default)]\npub(crate) struct HoverDefinition {\n    /// The range of the symbol that triggered the hover.\n    symbol_range: Range<usize>,\n    pub(crate) locations: Rc<Vec<lsp_types::LocationLink>>,\n    last_location: Option<(Range<usize>, Rc<Vec<lsp_types::LocationLink>>)>,\n}\n\nimpl HoverDefinition {\n    pub(crate) fn update(\n        &mut self,\n        symbol_range: Range<usize>,\n        locations: Vec<lsp_types::LocationLink>,\n    ) {\n        self.clear();\n        self.symbol_range = symbol_range;\n        self.locations = Rc::new(locations);\n    }\n\n    pub(crate) fn is_empty(&self) -> bool {\n        self.locations.is_empty()\n    }\n\n    pub(crate) fn clear(&mut self) {\n        if !self.locations.is_empty() {\n            self.last_location = Some((self.symbol_range.clone(), self.locations.clone()));\n        }\n\n        self.symbol_range = 0..0;\n        self.locations = Rc::new(vec![]);\n    }\n\n    pub(crate) fn is_same(&self, offset: usize) -> bool {\n        self.symbol_range.contains(&offset)\n    }\n}\n\nimpl InputState {\n    pub(crate) fn handle_hover_definition(\n        &mut self,\n        offset: usize,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let Some(provider) = self.lsp.definition_provider.clone() else {\n            return;\n        };\n\n        if self.hover_definition.is_same(offset) {\n            return;\n        }\n\n        // Currently not implemented.\n        let task = provider.definitions(&self.text, offset, window, cx);\n        let mut symbol_range = self.text.word_range(offset).unwrap_or(offset..offset);\n        let editor = cx.entity();\n        self.lsp._hover_task = cx.spawn_in(window, async move |_, cx| {\n            let locations = task.await?;\n\n            _ = editor.update(cx, |editor, cx| {\n                if locations.is_empty() {\n                    editor.hover_definition.clear();\n                } else {\n                    if let Some(location) = locations.first() {\n                        if let Some(range) = location.origin_selection_range {\n                            let start = editor.text.position_to_offset(&range.start);\n                            let end = editor.text.position_to_offset(&range.end);\n                            symbol_range = start..end;\n                        }\n                    }\n\n                    editor\n                        .hover_definition\n                        .update(symbol_range.clone(), locations.clone());\n                }\n                cx.notify();\n            });\n\n            Ok(())\n        });\n    }\n\n    pub(crate) fn on_action_go_to_definition(\n        &mut self,\n        _: &GoToDefinition,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let offset = self.cursor();\n        if let Some((symbol_range, locations)) = self.hover_definition.last_location.clone() {\n            if !(symbol_range.start..=symbol_range.end).contains(&offset) {\n                return;\n            }\n\n            if let Some(location) = locations.first().cloned() {\n                self.go_to_definition(&location, cx);\n            }\n        }\n    }\n\n    /// Return true if handled.\n    pub(crate) fn handle_click_hover_definition(\n        &mut self,\n        event: &MouseDownEvent,\n        offset: usize,\n        _: &mut Window,\n        cx: &mut Context<InputState>,\n    ) -> bool {\n        if !event.modifiers.secondary() {\n            return false;\n        }\n\n        if self.hover_definition.is_empty() {\n            return false;\n        };\n        if !self.hover_definition.is_same(offset) {\n            return false;\n        }\n\n        let Some(location) = self.hover_definition.locations.first().cloned() else {\n            return false;\n        };\n\n        self.go_to_definition(&location, cx);\n\n        true\n    }\n\n    pub(crate) fn go_to_definition(\n        &mut self,\n        location: &lsp_types::LocationLink,\n        cx: &mut Context<Self>,\n    ) {\n        if location\n            .target_uri\n            .scheme()\n            .map(|s| s.as_str() == \"https\" || s.as_str() == \"http\")\n            == Some(true)\n        {\n            cx.open_url(&location.target_uri.to_string());\n        } else {\n            // Move to the location.\n            let target_range = location.target_range;\n            let start = self.text.position_to_offset(&target_range.start);\n            let end = self.text.position_to_offset(&target_range.end);\n\n            self.move_to(start, None, cx);\n            self.select_to(end, cx);\n        }\n    }\n}\n\nimpl TextElement {\n    pub(crate) fn layout_hover_definition(\n        &self,\n        cx: &App,\n    ) -> Option<(Range<usize>, HighlightStyle)> {\n        let editor = self.state.read(cx);\n        if !editor.mode.is_code_editor() {\n            return None;\n        }\n\n        if editor.hover_definition.is_empty() {\n            return None;\n        };\n\n        let mut highlight_style: HighlightStyle = cx\n            .theme()\n            .highlight_theme\n            .link_text\n            .map(|style| style.into())\n            .unwrap_or_default();\n\n        highlight_style.underline = Some(UnderlineStyle {\n            thickness: px(1.),\n            ..UnderlineStyle::default()\n        });\n\n        Some((\n            editor.hover_definition.symbol_range.clone(),\n            highlight_style,\n        ))\n    }\n\n    pub(crate) fn layout_hover_definition_hitbox(\n        &self,\n        editor: &InputState,\n        window: &mut Window,\n        _cx: &App,\n    ) -> Option<Hitbox> {\n        if !editor.mode.is_code_editor() {\n            return None;\n        }\n\n        if editor.hover_definition.is_empty() {\n            return None;\n        };\n\n        let Some(bounds) = editor.range_to_bounds(&editor.hover_definition.symbol_range) else {\n            return None;\n        };\n\n        Some(window.insert_hitbox(bounds, gpui::HitboxBehavior::Normal))\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/lsp/document_colors.rs",
    "content": "use std::ops::Range;\nuse instant::Duration;\nuse anyhow::Result;\nuse gpui::{App, Context, Hsla, Task, Window};\nuse lsp_types::ColorInformation;\nuse ropey::Rope;\n\nuse crate::input::{InputState, Lsp, RopeExt};\n\npub trait DocumentColorProvider {\n    /// Fetches document colors for the specified range.\n    ///\n    /// textDocument/documentColor\n    ///\n    /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentColor\n    fn document_colors(\n        &self,\n        _text: &Rope,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Task<Result<Vec<ColorInformation>>>;\n}\n\nimpl Lsp {\n    /// Get document colors that intersect with the visible range (0-based row).\n    ///\n    /// Returns byte ranges and colors.\n    pub(crate) fn document_colors_for_range(\n        &self,\n        text: &Rope,\n        visible_range: &Range<usize>,\n    ) -> Vec<(Range<usize>, Hsla)> {\n        self.document_colors\n            .iter()\n            .filter_map(|(range, color)| {\n                if (range.start.line as usize) > visible_range.end\n                    || (range.end.line as usize) < visible_range.start\n                {\n                    return None;\n                }\n\n                let start = text.position_to_offset(&range.start);\n                let end = text.position_to_offset(&range.end);\n\n                Some((start..end, *color))\n            })\n            .collect()\n    }\n\n    pub(crate) fn update_document_colors(\n        &mut self,\n        text: &Rope,\n        window: &mut Window,\n        cx: &mut Context<InputState>,\n    ) {\n        let Some(provider) = self.document_color_provider.as_ref() else {\n            return;\n        };\n\n        let provider = provider.clone();\n        let text = text.clone();\n        let input_state = cx.entity();\n\n        // debounce timer 100ms\n        self._document_color_task = cx.spawn_in(window, async move |_, cx| {\n            cx.background_executor()\n                .timer(Duration::from_millis(100))\n                .await;\n\n            let task_result = cx\n                .update(|window, cx| provider.document_colors(&text, window, cx))\n                .ok();\n\n            if let Some(task) = task_result {\n                if let Ok(colors) = task.await {\n                    let _ = input_state.update(cx, |input_state, cx| {\n                        let mut document_colors: Vec<(lsp_types::Range, Hsla)> = colors\n                            .iter()\n                            .map(|info| {\n                                let color = gpui::Rgba {\n                                    r: info.color.red,\n                                    g: info.color.green,\n                                    b: info.color.blue,\n                                    a: info.color.alpha,\n                                }\n                                .into();\n\n                                (info.range, color)\n                            })\n                            .collect();\n                        document_colors.sort_by_key(|(range, _)| range.start);\n\n                        if document_colors != input_state.lsp.document_colors {\n                            input_state.lsp.document_colors = document_colors;\n                            cx.notify();\n                        }\n                    });\n                }\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/lsp/hover.rs",
    "content": "use instant::Duration;\nuse anyhow::Result;\nuse gpui::{App, Context, Task, Window};\nuse ropey::Rope;\n\nuse crate::input::{popovers::HoverPopover, InputState, RopeExt};\n\n/// Hover provider\n///\n/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover\npub trait HoverProvider {\n    /// textDocument/hover\n    ///\n    /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover\n    fn hover(\n        &self,\n        _text: &Rope,\n        _offset: usize,\n        _window: &mut Window,\n        _cx: &mut App,\n    ) -> Task<Result<Option<lsp_types::Hover>>>;\n}\n\nimpl InputState {\n    /// Handle hover trigger LSP request.\n    pub(super) fn handle_hover_popover(\n        &mut self,\n        offset: usize,\n        window: &mut Window,\n        cx: &mut Context<InputState>,\n    ) {\n        if self.selecting {\n            return;\n        }\n\n        let Some(provider) = self.lsp.hover_provider.clone() else {\n            return;\n        };\n\n        if let Some(hover_popover) = self.hover_popover.as_ref() {\n            if hover_popover.read(cx).is_same(offset) {\n                return;\n            }\n        }\n\n        // Currently not implemented.\n        let task = provider.hover(&self.text, offset, window, cx);\n        let mut symbol_range = self.text.word_range(offset).unwrap_or(offset..offset);\n        let editor = cx.entity();\n        let should_delay = self.hover_popover.is_none();\n        self.lsp._hover_task = cx.spawn_in(window, async move |_, cx| {\n            if should_delay {\n                cx.background_executor()\n                    .timer(Duration::from_millis(150))\n                    .await;\n            }\n\n            let result = task.await?;\n\n            _ = editor.update(cx, |editor, cx| match result {\n                Some(hover) => {\n                    if let Some(range) = hover.range {\n                        let start = editor.text.position_to_offset(&range.start);\n                        let end = editor.text.position_to_offset(&range.end);\n                        symbol_range = start..end;\n                    }\n                    let hover_popover = HoverPopover::new(cx.entity(), symbol_range, &hover, cx);\n                    editor.hover_popover = Some(hover_popover);\n                }\n                None => {\n                    editor.hover_popover = None;\n                }\n            });\n\n            Ok(())\n        });\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/lsp/mod.rs",
    "content": "use anyhow::Result;\nuse gpui::{App, Context, Hsla, MouseMoveEvent, Task, Window};\nuse ropey::Rope;\nuse std::rc::Rc;\n\nuse crate::input::{InputState, RopeExt, popovers::ContextMenu};\n\nmod code_actions;\nmod completions;\nmod definitions;\nmod document_colors;\nmod hover;\n\npub use code_actions::*;\npub use completions::*;\npub use definitions::*;\npub use document_colors::*;\npub use hover::*;\n\n/// LSP ServerCapabilities\n///\n/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#serverCapabilities\npub struct Lsp {\n    /// The completion provider.\n    pub completion_provider: Option<Rc<dyn CompletionProvider>>,\n    /// The code action providers.\n    pub code_action_providers: Vec<Rc<dyn CodeActionProvider>>,\n    /// The hover provider.\n    pub hover_provider: Option<Rc<dyn HoverProvider>>,\n    /// The definition provider.\n    pub definition_provider: Option<Rc<dyn DefinitionProvider>>,\n    /// The document color provider.\n    pub document_color_provider: Option<Rc<dyn DocumentColorProvider>>,\n\n    document_colors: Vec<(lsp_types::Range, Hsla)>,\n    _hover_task: Task<Result<()>>,\n    _document_color_task: Task<()>,\n}\n\nimpl Default for Lsp {\n    fn default() -> Self {\n        Self {\n            completion_provider: None,\n            code_action_providers: vec![],\n            hover_provider: None,\n            definition_provider: None,\n            document_color_provider: None,\n            document_colors: vec![],\n            _hover_task: Task::ready(Ok(())),\n            _document_color_task: Task::ready(()),\n        }\n    }\n}\n\nimpl Lsp {\n    /// Update the LSP when the text changes.\n    pub(crate) fn update(\n        &mut self,\n        text: &Rope,\n        window: &mut Window,\n        cx: &mut Context<InputState>,\n    ) {\n        self.update_document_colors(text, window, cx);\n    }\n\n    /// Reset all LSP states.\n    pub(crate) fn reset(&mut self) {\n        self.document_colors.clear();\n        self._hover_task = Task::ready(Ok(()));\n        self._document_color_task = Task::ready(());\n    }\n}\n\nimpl InputState {\n    pub(crate) fn hide_context_menu(&mut self, cx: &mut Context<Self>) {\n        self.context_menu = None;\n        self._context_menu_task = Task::ready(Ok(()));\n        cx.notify();\n    }\n\n    pub(crate) fn is_context_menu_open(&self, cx: &App) -> bool {\n        let Some(menu) = self.context_menu.as_ref() else {\n            return false;\n        };\n\n        menu.is_open(cx)\n    }\n\n    /// Handles an action for the completion menu, if it exists.\n    ///\n    /// Return true if the action was handled, otherwise false.\n    pub fn handle_action_for_context_menu(\n        &mut self,\n        action: Box<dyn gpui::Action>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> bool {\n        let Some(menu) = self.context_menu.as_ref() else {\n            return false;\n        };\n\n        let mut handled = false;\n\n        match menu {\n            ContextMenu::Completion(menu) => {\n                _ = menu.update(cx, |menu, cx| {\n                    handled = menu.handle_action(action, window, cx)\n                });\n            }\n            ContextMenu::CodeAction(menu) => {\n                _ = menu.update(cx, |menu, cx| {\n                    handled = menu.handle_action(action, window, cx)\n                });\n            }\n            ContextMenu::MouseContext(..) => {}\n        };\n\n        handled\n    }\n\n    /// Apply a list of [`lsp_types::TextEdit`] to mutate the text.\n    pub fn apply_lsp_edits(\n        &mut self,\n        text_edits: &Vec<lsp_types::TextEdit>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        for edit in text_edits {\n            let start = self.text.position_to_offset(&edit.range.start);\n            let end = self.text.position_to_offset(&edit.range.end);\n\n            let range_utf16 = self.range_to_utf16(&(start..end));\n            self.replace_text_in_range_silent(Some(range_utf16), &edit.new_text, window, cx);\n        }\n    }\n\n    pub(super) fn handle_mouse_move(\n        &mut self,\n        offset: usize,\n        event: &MouseMoveEvent,\n        window: &mut Window,\n        cx: &mut Context<InputState>,\n    ) {\n        if event.modifiers.secondary() {\n            self.handle_hover_definition(offset, window, cx);\n        } else {\n            self.hover_definition.clear();\n            self.handle_hover_popover(offset, window, cx);\n        }\n        cx.notify();\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/mask_pattern.rs",
    "content": "use gpui::SharedString;\n\n#[derive(Clone, PartialEq, Debug)]\npub enum MaskToken {\n    /// 0 Digit, equivalent to `[0]`\n    // Digit0,\n    /// Digit, equivalent to `[0-9]`\n    Digit,\n    /// Letter, equivalent to `[a-zA-Z]`\n    Letter,\n    /// Letter or digit, equivalent to `[a-zA-Z0-9]`\n    LetterOrDigit,\n    /// Separator\n    Sep(char),\n    /// Any character\n    Any,\n}\n\n#[allow(unused)]\nimpl MaskToken {\n    /// Check if the token is any character.\n    pub fn is_any(&self) -> bool {\n        matches!(self, MaskToken::Any)\n    }\n\n    /// Check if the token is a match for the given character.\n    ///\n    /// The separator is always a match any input character.\n    fn is_match(&self, ch: char) -> bool {\n        match self {\n            MaskToken::Digit => ch.is_ascii_digit(),\n            MaskToken::Letter => ch.is_ascii_alphabetic(),\n            MaskToken::LetterOrDigit => ch.is_ascii_alphanumeric(),\n            MaskToken::Any => true,\n            MaskToken::Sep(c) => *c == ch,\n        }\n    }\n\n    /// Is the token a separator (Can be ignored)\n    fn is_sep(&self) -> bool {\n        matches!(self, MaskToken::Sep(_))\n    }\n\n    /// Check if the token is a number.\n    pub fn is_number(&self) -> bool {\n        matches!(self, MaskToken::Digit)\n    }\n\n    pub fn placeholder(&self) -> char {\n        match self {\n            MaskToken::Sep(c) => *c,\n            _ => '_',\n        }\n    }\n\n    fn mask_char(&self, ch: char) -> char {\n        match self {\n            MaskToken::Digit | MaskToken::LetterOrDigit | MaskToken::Letter => ch,\n            MaskToken::Sep(c) => *c,\n            MaskToken::Any => ch,\n        }\n    }\n\n    fn unmask_char(&self, ch: char) -> Option<char> {\n        match self {\n            MaskToken::Digit => Some(ch),\n            MaskToken::Letter => Some(ch),\n            MaskToken::LetterOrDigit => Some(ch),\n            MaskToken::Any => Some(ch),\n            _ => None,\n        }\n    }\n}\n\n#[derive(Clone, Default)]\npub enum MaskPattern {\n    #[default]\n    None,\n    Pattern {\n        pattern: SharedString,\n        tokens: Vec<MaskToken>,\n    },\n    Number {\n        /// Group separator, e.g. \",\" or \" \"\n        separator: Option<char>,\n        /// Number of fraction digits, e.g. 2 for 123.45\n        fraction: Option<usize>,\n    },\n}\n\nimpl From<&str> for MaskPattern {\n    fn from(pattern: &str) -> Self {\n        Self::new(pattern)\n    }\n}\n\nimpl MaskPattern {\n    /// Create a new mask pattern\n    ///\n    /// - `9` - Digit\n    /// - `A` - Letter\n    /// - `#` - Letter or Digit\n    /// - `*` - Any character\n    /// - other characters - Separator\n    ///\n    /// For example:\n    ///\n    /// - `(999)999-9999` - US phone number: (123)456-7890\n    /// - `99999-9999` - ZIP code: 12345-6789\n    /// - `AAAA-99-####` - Custom pattern: ABCD-12-3AB4\n    /// - `*999*` - Custom pattern: (123) or [123]\n    pub fn new(pattern: &str) -> Self {\n        let tokens = pattern\n            .chars()\n            .map(|ch| match ch {\n                // '0' => MaskToken::Digit0,\n                '9' => MaskToken::Digit,\n                'A' => MaskToken::Letter,\n                '#' => MaskToken::LetterOrDigit,\n                '*' => MaskToken::Any,\n                _ => MaskToken::Sep(ch),\n            })\n            .collect();\n\n        Self::Pattern {\n            pattern: pattern.to_owned().into(),\n            tokens,\n        }\n    }\n\n    #[allow(unused)]\n    fn tokens(&self) -> Option<&Vec<MaskToken>> {\n        match self {\n            Self::Pattern { tokens, .. } => Some(tokens),\n            Self::Number { .. } => None,\n            Self::None => None,\n        }\n    }\n\n    /// Create a new mask pattern with group separator, e.g. \",\" or \" \"\n    pub fn number(sep: Option<char>) -> Self {\n        Self::Number {\n            separator: sep,\n            fraction: None,\n        }\n    }\n\n    pub fn placeholder(&self) -> Option<String> {\n        match self {\n            Self::Pattern { tokens, .. } => {\n                Some(tokens.iter().map(|token| token.placeholder()).collect())\n            }\n            Self::Number { .. } => None,\n            Self::None => None,\n        }\n    }\n\n    /// Return true if the mask pattern is None or no any pattern.\n    pub fn is_none(&self) -> bool {\n        match self {\n            Self::Pattern { tokens, .. } => tokens.is_empty(),\n            Self::Number { .. } => false,\n            Self::None => true,\n        }\n    }\n\n    /// Check is the mask text is valid.\n    ///\n    /// If the mask pattern is None, always return true.\n    pub fn is_valid(&self, mask_text: &str) -> bool {\n        if self.is_none() {\n            return true;\n        }\n\n        let mut text_index = 0;\n        let mask_text_chars: Vec<char> = mask_text.chars().collect();\n        match self {\n            Self::Pattern { tokens, .. } => {\n                for token in tokens {\n                    if text_index >= mask_text_chars.len() {\n                        break;\n                    }\n\n                    let ch = mask_text_chars[text_index];\n                    if token.is_match(ch) {\n                        text_index += 1;\n                    }\n                }\n                text_index == mask_text.len()\n            }\n            Self::Number { separator, .. } => {\n                if mask_text.is_empty() {\n                    return true;\n                }\n\n                // check if the text is valid number\n                let mut parts = mask_text.split('.');\n                let int_part = parts.next().unwrap_or(\"\");\n                let frac_part = parts.next();\n\n                if int_part.is_empty() {\n                    return false;\n                }\n\n                let sign_positions: Vec<usize> = int_part\n                    .chars()\n                    .enumerate()\n                    .filter_map(|(i, ch)| match is_sign(&ch) {\n                        true => Some(i),\n                        false => None,\n                    })\n                    .collect();\n\n                // only one sign is valid\n                // sign is only valid at the beginning of the string\n                if sign_positions.len() > 1 || sign_positions.first() > Some(&0) {\n                    return false;\n                }\n\n                // check if the integer part is valid\n                if !int_part.chars().enumerate().all(|(i, ch)| {\n                    ch.is_ascii_digit() || is_sign(&ch) && i == 0 || Some(ch) == *separator\n                }) {\n                    return false;\n                }\n\n                // check if the fraction part is valid\n                if let Some(frac) = frac_part {\n                    if !frac\n                        .chars()\n                        .all(|ch| ch.is_ascii_digit() || Some(ch) == *separator)\n                    {\n                        return false;\n                    }\n                }\n\n                true\n            }\n            Self::None => true,\n        }\n    }\n\n    /// Check if valid input char at the given position.\n    pub fn is_valid_at(&self, ch: char, pos: usize) -> bool {\n        if self.is_none() {\n            return true;\n        }\n\n        match self {\n            Self::Pattern { tokens, .. } => {\n                if let Some(token) = tokens.get(pos) {\n                    if token.is_match(ch) {\n                        return true;\n                    }\n\n                    if token.is_sep() {\n                        // If next token is match, it's valid\n                        if let Some(next_token) = tokens.get(pos + 1) {\n                            if next_token.is_match(ch) {\n                                return true;\n                            }\n                        }\n                    }\n                }\n\n                false\n            }\n            Self::Number { .. } => true,\n            Self::None => true,\n        }\n    }\n\n    /// Format the text according to the mask pattern\n    ///\n    /// For example:\n    ///\n    /// - pattern: (999)999-999\n    /// - text: 123456789\n    /// - mask_text: (123)456-789\n    pub fn mask(&self, text: &str) -> SharedString {\n        if self.is_none() {\n            return text.to_owned().into();\n        }\n\n        match self {\n            Self::Number {\n                separator,\n                fraction,\n            } => {\n                if let Some(sep) = *separator {\n                    // Remove the existing group separator\n                    let text = text.replace(sep, \"\");\n\n                    let mut parts = text.split('.');\n                    let int_part = parts.next().unwrap_or(\"\");\n\n                    // Limit the fraction part to the given range, if not enough, pad with 0\n                    let frac_part = parts.next().map(|part| {\n                        part.chars()\n                            .take(fraction.unwrap_or(usize::MAX))\n                            .collect::<String>()\n                    });\n\n                    // Reverse the integer part for easier grouping\n                    let mut chars: Vec<char> = int_part.chars().rev().collect();\n\n                    // Removing the sign from formatting to avoid cases such as: -,123\n                    let maybe_signed = if let Some(pos) = chars.iter().position(is_sign) {\n                        Some(chars.remove(pos))\n                    } else {\n                        None\n                    };\n\n                    let mut result = String::new();\n                    for (i, ch) in chars.iter().enumerate() {\n                        if i > 0 && i % 3 == 0 {\n                            result.push(sep);\n                        }\n                        result.push(*ch);\n                    }\n                    let int_with_sep: String = result.chars().rev().collect();\n\n                    let final_str = if let Some(frac) = frac_part {\n                        if fraction == &Some(0) {\n                            int_with_sep\n                        } else {\n                            format!(\"{}.{}\", int_with_sep, frac)\n                        }\n                    } else {\n                        int_with_sep\n                    };\n\n                    let final_str = if let Some(sign) = maybe_signed {\n                        format!(\"{}{}\", sign, final_str)\n                    } else {\n                        final_str\n                    };\n\n                    return final_str.into();\n                }\n\n                text.to_owned().into()\n            }\n            Self::Pattern { tokens, .. } => {\n                let mut result = String::new();\n                let mut text_index = 0;\n                let text_chars: Vec<char> = text.chars().collect();\n                for (pos, token) in tokens.iter().enumerate() {\n                    if text_index >= text_chars.len() {\n                        break;\n                    }\n                    let ch = text_chars[text_index];\n                    // Break if expected char is not match\n                    if !token.is_sep() && !self.is_valid_at(ch, pos) {\n                        break;\n                    }\n                    let mask_ch = token.mask_char(ch);\n                    result.push(mask_ch);\n                    if ch == mask_ch {\n                        text_index += 1;\n                        continue;\n                    }\n                }\n                result.into()\n            }\n            Self::None => text.to_owned().into(),\n        }\n    }\n\n    /// Extract original text from masked text\n    pub fn unmask(&self, mask_text: &str) -> String {\n        match self {\n            Self::Number { separator, .. } => {\n                if let Some(sep) = *separator {\n                    let mut result = String::new();\n                    for ch in mask_text.chars() {\n                        if ch == sep {\n                            continue;\n                        }\n                        result.push(ch);\n                    }\n\n                    if result.contains('.') {\n                        result = result.trim_end_matches('0').to_string();\n                    }\n                    return result;\n                }\n\n                return mask_text.to_owned();\n            }\n            Self::Pattern { tokens, .. } => {\n                let mut result = String::new();\n                let mask_text_chars: Vec<char> = mask_text.chars().collect();\n                for (text_index, token) in tokens.iter().enumerate() {\n                    if text_index >= mask_text_chars.len() {\n                        break;\n                    }\n                    let ch = mask_text_chars[text_index];\n                    let unmask_ch = token.unmask_char(ch);\n                    if let Some(ch) = unmask_ch {\n                        result.push(ch);\n                    }\n                }\n                result\n            }\n            Self::None => mask_text.to_owned(),\n        }\n    }\n}\n\n#[inline]\nfn is_sign(ch: &char) -> bool {\n    matches!(ch, '+' | '-')\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::input::mask_pattern::{MaskPattern, MaskToken};\n\n    #[test]\n    fn test_is_match() {\n        assert_eq!(MaskToken::Sep('(').is_match('('), true);\n        assert_eq!(MaskToken::Sep('-').is_match('('), false);\n        assert_eq!(MaskToken::Sep('-').is_match('3'), false);\n\n        assert_eq!(MaskToken::Digit.is_match('0'), true);\n        assert_eq!(MaskToken::Digit.is_match('9'), true);\n        assert_eq!(MaskToken::Digit.is_match('a'), false);\n        assert_eq!(MaskToken::Digit.is_match('C'), false);\n\n        assert_eq!(MaskToken::Letter.is_match('a'), true);\n        assert_eq!(MaskToken::Letter.is_match('Z'), true);\n        assert_eq!(MaskToken::Letter.is_match('3'), false);\n        assert_eq!(MaskToken::Letter.is_match('-'), false);\n\n        assert_eq!(MaskToken::LetterOrDigit.is_match('0'), true);\n        assert_eq!(MaskToken::LetterOrDigit.is_match('9'), true);\n        assert_eq!(MaskToken::LetterOrDigit.is_match('a'), true);\n        assert_eq!(MaskToken::LetterOrDigit.is_match('Z'), true);\n        assert_eq!(MaskToken::LetterOrDigit.is_match('3'), true);\n\n        assert_eq!(MaskToken::Any.is_match('a'), true);\n        assert_eq!(MaskToken::Any.is_match('3'), true);\n        assert_eq!(MaskToken::Any.is_match('-'), true);\n        assert_eq!(MaskToken::Any.is_match(' '), true);\n    }\n\n    #[test]\n    fn test_mask_none() {\n        let mask = MaskPattern::None;\n        assert_eq!(mask.is_none(), true);\n        assert_eq!(mask.is_valid(\"1124124ASLDJKljk\"), true);\n        assert_eq!(mask.mask(\"hello-world\"), \"hello-world\");\n        assert_eq!(mask.unmask(\"hello-world\"), \"hello-world\");\n    }\n\n    #[test]\n    fn test_mask_pattern1() {\n        let mask = MaskPattern::new(\"(AA)999-999\");\n        assert_eq!(\n            mask.tokens(),\n            Some(&vec![\n                MaskToken::Sep('('),\n                MaskToken::Letter,\n                MaskToken::Letter,\n                MaskToken::Sep(')'),\n                MaskToken::Digit,\n                MaskToken::Digit,\n                MaskToken::Digit,\n                MaskToken::Sep('-'),\n                MaskToken::Digit,\n                MaskToken::Digit,\n                MaskToken::Digit,\n            ])\n        );\n\n        assert_eq!(mask.is_valid_at('(', 0), true);\n        assert_eq!(mask.is_valid_at('H', 0), true);\n        assert_eq!(mask.is_valid_at('3', 0), false);\n        assert_eq!(mask.is_valid_at('-', 0), false);\n        assert_eq!(mask.is_valid_at(')', 1), false);\n        assert_eq!(mask.is_valid_at('H', 1), true);\n        assert_eq!(mask.is_valid_at('1', 1), false);\n        assert_eq!(mask.is_valid_at('e', 2), true);\n        assert_eq!(mask.is_valid_at(')', 3), true);\n        assert_eq!(mask.is_valid_at('1', 3), true);\n        assert_eq!(mask.is_valid_at('2', 4), true);\n\n        assert_eq!(mask.is_valid(\"(AB)123-456\"), true);\n\n        assert_eq!(mask.mask(\"AB123456\"), \"(AB)123-456\");\n        assert_eq!(mask.mask(\"(AB)123-456\"), \"(AB)123-456\");\n        assert_eq!(mask.mask(\"(AB123456\"), \"(AB)123-456\");\n        assert_eq!(mask.mask(\"AB123-456\"), \"(AB)123-456\");\n        assert_eq!(mask.mask(\"AB123-\"), \"(AB)123-\");\n        assert_eq!(mask.mask(\"AB123--\"), \"(AB)123-\");\n        assert_eq!(mask.mask(\"AB123-4\"), \"(AB)123-4\");\n\n        let unmasked_text = mask.unmask(\"(AB)123-456\");\n        assert_eq!(unmasked_text, \"AB123456\");\n\n        assert_eq!(mask.is_valid(\"12AB345\"), false);\n        assert_eq!(mask.is_valid(\"(11)123-456\"), false);\n        assert_eq!(mask.is_valid(\"##\"), false);\n        assert_eq!(mask.is_valid(\"(AB)123456\"), true);\n    }\n\n    #[test]\n    fn test_mask_pattern2() {\n        let mask = MaskPattern::new(\"999-999-******\");\n        assert_eq!(\n            mask.tokens(),\n            Some(&vec![\n                MaskToken::Digit,\n                MaskToken::Digit,\n                MaskToken::Digit,\n                MaskToken::Sep('-'),\n                MaskToken::Digit,\n                MaskToken::Digit,\n                MaskToken::Digit,\n                MaskToken::Sep('-'),\n                MaskToken::Any,\n                MaskToken::Any,\n                MaskToken::Any,\n                MaskToken::Any,\n                MaskToken::Any,\n                MaskToken::Any,\n            ])\n        );\n\n        let text = \"123456A(111)\";\n        let masked_text = mask.mask(text);\n        assert_eq!(masked_text, \"123-456-A(111)\");\n        let unmasked_text = mask.unmask(&masked_text);\n        assert_eq!(unmasked_text, \"123456A(111)\");\n        assert_eq!(mask.is_valid(&masked_text), true);\n    }\n\n    #[test]\n    fn test_number_with_group_separator() {\n        // Use comma as group separator\n        let mask = MaskPattern::number(Some(','));\n        assert_eq!(mask.mask(\"1234567\"), \"1,234,567\");\n        assert_eq!(mask.mask(\"1,234,567\"), \"1,234,567\");\n        assert_eq!(mask.unmask(\"1,234,567\"), \"1234567\");\n        let mask = MaskPattern::number(Some(','));\n        assert_eq!(mask.mask(\"1234567.89\"), \"1,234,567.89\");\n        assert_eq!(mask.unmask(\"1,234,567.89\"), \"1234567.89\");\n\n        // Use space as group separator\n        let mask = MaskPattern::number(Some(' '));\n        assert_eq!(mask.mask(\"1234567\"), \"1 234 567\");\n        assert_eq!(mask.unmask(\"1 234 567\"), \"1234567\");\n        let mask = MaskPattern::number(Some(' '));\n        assert_eq!(mask.mask(\"1234567.89\"), \"1 234 567.89\");\n        assert_eq!(mask.unmask(\"1 234 567.89\"), \"1234567.89\");\n\n        // No group separator\n        let mask = MaskPattern::number(None);\n        assert_eq!(mask.mask(\"1234567\"), \"1234567\");\n        assert_eq!(mask.unmask(\"1234567\"), \"1234567\");\n        let mask = MaskPattern::number(None);\n        assert_eq!(mask.mask(\"1234567.89\"), \"1234567.89\");\n        assert_eq!(mask.unmask(\"1234567.89\"), \"1234567.89\");\n    }\n\n    #[test]\n    fn test_number_with_fraction_digits() {\n        let mask = MaskPattern::Number {\n            separator: Some(','),\n            fraction: Some(4),\n        };\n\n        assert_eq!(mask.mask(\"1234567\"), \"1,234,567\");\n        assert_eq!(mask.unmask(\"1,234,567\"), \"1234567\");\n        assert_eq!(mask.mask(\"1234567.\"), \"1,234,567.\");\n        assert_eq!(mask.mask(\"1234567.89\"), \"1,234,567.89\");\n        assert_eq!(mask.unmask(\"1,234,567.890\"), \"1234567.89\");\n        assert_eq!(mask.mask(\"1234567.891\"), \"1,234,567.891\");\n        assert_eq!(mask.mask(\"1234567.891234\"), \"1,234,567.8912\");\n\n        let mask = MaskPattern::Number {\n            separator: Some(','),\n            fraction: None,\n        };\n\n        assert_eq!(mask.mask(\"1234567.1234567\"), \"1,234,567.1234567\");\n\n        let mask = MaskPattern::Number {\n            separator: Some(','),\n            fraction: Some(0),\n        };\n\n        assert_eq!(mask.mask(\"1234567.1234567\"), \"1,234,567\");\n    }\n\n    #[test]\n    fn test_signed_number_numbers() {\n        let mask = MaskPattern::Number {\n            separator: Some(','),\n            fraction: Some(2),\n        };\n\n        assert_eq!(mask.is_valid(\"-\"), true);\n        assert_eq!(mask.is_valid(\"-1234567\"), true);\n        assert_eq!(mask.is_valid(\"-1,234,567\"), true);\n        assert_eq!(mask.is_valid(\"-1234567.\"), true);\n        assert_eq!(mask.is_valid(\"-1234567.89\"), true);\n\n        assert_eq!(mask.is_valid(\"+\"), true);\n        assert_eq!(mask.is_valid(\"+1234567\"), true);\n        assert_eq!(mask.is_valid(\"+1,234,567\"), true);\n        assert_eq!(mask.is_valid(\"+1234567.\"), true);\n        assert_eq!(mask.is_valid(\"+1234567.89\"), true);\n\n        // Only one sign is valid\n        assert_eq!(mask.is_valid(\"+-\"), false);\n        assert_eq!(mask.is_valid(\"-+\"), false);\n        assert_eq!(mask.is_valid(\"+-1234567\"), false);\n\n        // No sign is valid in the middle of the number\n        assert_eq!(mask.is_valid(\"1,-234,567\"), false);\n        assert_eq!(mask.is_valid(\"12-34567.89\"), false);\n\n        // Signs in fractions are invalid\n        assert_eq!(mask.is_valid(\"+1234567.-\"), false);\n\n        // The separator does not show up before the sign i.e. -,123\n        assert_eq!(mask.mask(\"-123\"), \"-123\");\n\n        assert_eq!(mask.mask(\"-1234567\"), \"-1,234,567\");\n        assert_eq!(mask.mask(\"+1234567\"), \"+1,234,567\");\n        assert_eq!(mask.unmask(\"-1,234,567\"), \"-1234567\");\n        assert_eq!(mask.mask(\"-1234567.\"), \"-1,234,567.\");\n        assert_eq!(mask.mask(\"-1234567.89\"), \"-1,234,567.89\");\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/mod.rs",
    "content": "mod blink_cursor;\nmod change;\nmod clear_button;\nmod cursor;\nmod display_map;\nmod element;\nmod indent;\nmod input;\nmod lsp;\nmod mask_pattern;\nmod mode;\nmod movement;\nmod number_input;\nmod otp_input;\npub(crate) mod popovers;\nmod rope_ext;\nmod search;\nmod selection;\nmod state;\n\npub(crate) use clear_button::*;\npub use cursor::*;\n#[cfg(target_family = \"wasm\")]\npub use display_map::folding::Tree;\npub use display_map::{BufferPoint, DisplayMap, DisplayPoint, FoldRange};\npub use indent::TabSize;\npub use input::*;\npub use lsp::*;\npub use lsp_types::Position;\npub use mask_pattern::MaskPattern;\npub use number_input::{NumberInput, NumberInputEvent, StepAction};\npub use otp_input::*;\npub use rope_ext::{InputEdit, Point, RopeExt, RopeLines};\npub use ropey::Rope;\npub use state::*;\n"
  },
  {
    "path": "crates/ui/src/input/mode.rs",
    "content": "use std::rc::Rc;\nuse std::time::Duration;\nuse std::{cell::RefCell, ops::Range};\n\nuse gpui::{App, SharedString, Task};\nuse ropey::Rope;\n\nuse super::display_map::DisplayMap;\nuse crate::highlighter::DiagnosticSet;\nuse crate::highlighter::SyntaxHighlighter;\nuse crate::input::{InputEdit, RopeExt as _, TabSize};\n\n#[allow(dead_code)]\npub(super) struct PendingBackgroundParse {\n    pub highlighter: Rc<RefCell<Option<SyntaxHighlighter>>>,\n    pub parse_task: Rc<RefCell<Option<Task<()>>>>,\n    pub language: SharedString,\n    pub text: Rope,\n}\n\n#[derive(Clone)]\npub(crate) enum InputMode {\n    /// A plain text input mode.\n    PlainText {\n        multi_line: bool,\n        tab: TabSize,\n        rows: usize,\n    },\n    /// An auto grow input mode.\n    AutoGrow {\n        rows: usize,\n        min_rows: usize,\n        max_rows: usize,\n    },\n    /// A code editor input mode.\n    CodeEditor {\n        multi_line: bool,\n        tab: TabSize,\n        rows: usize,\n        /// Show line number\n        line_number: bool,\n        language: SharedString,\n        indent_guides: bool,\n        folding: bool,\n        highlighter: Rc<RefCell<Option<SyntaxHighlighter>>>,\n        diagnostics: DiagnosticSet,\n        parse_task: Rc<RefCell<Option<Task<()>>>>,\n    },\n}\n\nimpl Default for InputMode {\n    fn default() -> Self {\n        InputMode::plain_text()\n    }\n}\n\n#[allow(unused)]\nimpl InputMode {\n    /// Create a plain input mode with default settings.\n    pub(super) fn plain_text() -> Self {\n        InputMode::PlainText {\n            multi_line: false,\n            tab: TabSize::default(),\n            rows: 1,\n        }\n    }\n\n    /// Create a code editor input mode with default settings.\n    pub(super) fn code_editor(language: impl Into<SharedString>) -> Self {\n        InputMode::CodeEditor {\n            rows: 2,\n            multi_line: true,\n            tab: TabSize::default(),\n            language: language.into(),\n            highlighter: Rc::new(RefCell::new(None)),\n            line_number: true,\n            indent_guides: true,\n            folding: true,\n            diagnostics: DiagnosticSet::new(&Rope::new()),\n            parse_task: Rc::new(RefCell::new(None)),\n        }\n    }\n\n    /// Create an auto grow input mode with given min and max rows.\n    pub(super) fn auto_grow(min_rows: usize, max_rows: usize) -> Self {\n        InputMode::AutoGrow {\n            rows: min_rows,\n            min_rows,\n            max_rows,\n        }\n    }\n\n    pub(super) fn multi_line(mut self, multi_line: bool) -> Self {\n        match &mut self {\n            InputMode::PlainText { multi_line: ml, .. } => *ml = multi_line,\n            InputMode::CodeEditor { multi_line: ml, .. } => *ml = multi_line,\n            InputMode::AutoGrow { .. } => {}\n        }\n        self\n    }\n\n    #[inline]\n    pub(super) fn is_single_line(&self) -> bool {\n        !self.is_multi_line()\n    }\n\n    #[inline]\n    pub(super) fn is_code_editor(&self) -> bool {\n        matches!(self, InputMode::CodeEditor { .. })\n    }\n\n    /// Return true if the mode is code editor and `folding: true`, `multi_line: true`.\n    #[inline]\n    pub(crate) fn is_folding(&self) -> bool {\n        if cfg!(target_family = \"wasm\") {\n            return false;\n        }\n\n        matches!(\n            self,\n            InputMode::CodeEditor {\n                folding: true,\n                multi_line: true,\n                ..\n            }\n        )\n    }\n\n    #[inline]\n    pub(super) fn is_auto_grow(&self) -> bool {\n        matches!(self, InputMode::AutoGrow { .. })\n    }\n\n    #[inline]\n    pub(super) fn is_multi_line(&self) -> bool {\n        match self {\n            InputMode::PlainText { multi_line, .. } => *multi_line,\n            InputMode::CodeEditor { multi_line, .. } => *multi_line,\n            InputMode::AutoGrow { max_rows, .. } => *max_rows > 1,\n        }\n    }\n\n    pub(super) fn set_rows(&mut self, new_rows: usize) {\n        match self {\n            InputMode::PlainText { rows, .. } => {\n                *rows = new_rows;\n            }\n            InputMode::CodeEditor { rows, .. } => {\n                *rows = new_rows;\n            }\n            InputMode::AutoGrow {\n                rows,\n                min_rows,\n                max_rows,\n            } => {\n                *rows = new_rows.clamp(*min_rows, *max_rows);\n            }\n        }\n    }\n\n    pub(super) fn update_auto_grow(&mut self, display_map: &DisplayMap) {\n        if self.is_single_line() {\n            return;\n        }\n\n        let wrapped_lines = display_map.wrap_row_count();\n        self.set_rows(wrapped_lines);\n    }\n\n    /// At least 1 row be return.\n    pub(super) fn rows(&self) -> usize {\n        if !self.is_multi_line() {\n            return 1;\n        }\n\n        match self {\n            InputMode::PlainText { rows, .. } => *rows,\n            InputMode::CodeEditor { rows, .. } => *rows,\n            InputMode::AutoGrow { rows, .. } => *rows,\n        }\n        .max(1)\n    }\n\n    /// At least 1 row be return.\n    #[allow(unused)]\n    pub(super) fn min_rows(&self) -> usize {\n        match self {\n            InputMode::AutoGrow { min_rows, .. } => *min_rows,\n            _ => 1,\n        }\n        .max(1)\n    }\n\n    #[allow(unused)]\n    pub(super) fn max_rows(&self) -> usize {\n        if !self.is_multi_line() {\n            return 1;\n        }\n\n        match self {\n            InputMode::AutoGrow { max_rows, .. } => *max_rows,\n            _ => usize::MAX,\n        }\n    }\n\n    /// Return false if the mode is not [`InputMode::CodeEditor`].\n    #[inline]\n    pub(super) fn line_number(&self) -> bool {\n        match self {\n            InputMode::CodeEditor {\n                line_number,\n                multi_line,\n                ..\n            } => *line_number && *multi_line,\n            _ => false,\n        }\n    }\n\n    /// Update the syntax highlighter with new text.\n    ///\n    /// Returns `Some(PendingBackgroundParse)` when the synchronous parse\n    /// timed out and the caller should dispatch a background parse.\n    /// Returns `None` when parsing completed (or no highlighter is active).\n    pub(super) fn update_highlighter(\n        &mut self,\n        selected_range: &Range<usize>,\n        text: &Rope,\n        new_text: &str,\n        force: bool,\n        cx: &mut App,\n    ) -> Option<PendingBackgroundParse> {\n        match &self {\n            InputMode::CodeEditor {\n                language,\n                highlighter,\n                parse_task,\n                ..\n            } => {\n                if !force && highlighter.borrow().is_some() {\n                    return None;\n                }\n\n                let mut highlighter_ref = highlighter.borrow_mut();\n                if highlighter_ref.is_none() {\n                    let new_highlighter = SyntaxHighlighter::new(language);\n                    highlighter_ref.replace(new_highlighter);\n                }\n\n                let Some(h) = highlighter_ref.as_mut() else {\n                    return None;\n                };\n\n                // When full text changed, the selected_range may be out of bound (The before version).\n                let mut selected_range = selected_range.clone();\n                selected_range.end = selected_range.end.min(text.len());\n\n                // If insert a chart, this is 1.\n                // If backspace or delete, this is -1.\n                // If selected to delete, this is the length of the selected text.\n                // let changed_len = new_text.len() as isize - selected_range.len() as isize;\n                let changed_len = new_text.len() as isize - selected_range.len() as isize;\n                let new_end = (selected_range.end as isize + changed_len) as usize;\n\n                let start_pos = text.offset_to_point(selected_range.start);\n                let old_end_pos = text.offset_to_point(selected_range.end);\n                let new_end_pos = text.offset_to_point(new_end);\n\n                let edit = InputEdit {\n                    start_byte: selected_range.start,\n                    old_end_byte: selected_range.end,\n                    new_end_byte: new_end,\n                    start_position: start_pos,\n                    old_end_position: old_end_pos,\n                    new_end_position: new_end_pos,\n                };\n\n                const SYNC_PARSE_TIMEOUT: Duration = Duration::from_millis(2);\n                let completed = h.update(Some(edit), text, Some(SYNC_PARSE_TIMEOUT));\n                if completed {\n                    // Sync parse succeeded, cancel any pending background parse.\n                    parse_task.borrow_mut().take();\n                    None\n                } else {\n                    // Timed out. Return the data needed for background parsing.\n                    let pending = PendingBackgroundParse {\n                        language: h.language().clone(),\n                        text: text.clone(),\n                        highlighter: highlighter.clone(),\n                        parse_task: parse_task.clone(),\n                    };\n                    drop(highlighter_ref);\n                    Some(pending)\n                }\n            }\n            _ => None,\n        }\n    }\n\n    #[allow(unused)]\n    pub(super) fn diagnostics(&self) -> Option<&DiagnosticSet> {\n        match self {\n            InputMode::CodeEditor { diagnostics, .. } => Some(diagnostics),\n            _ => None,\n        }\n    }\n\n    pub(super) fn diagnostics_mut(&mut self) -> Option<&mut DiagnosticSet> {\n        match self {\n            InputMode::CodeEditor { diagnostics, .. } => Some(diagnostics),\n            _ => None,\n        }\n    }\n\n    /// Get a reference to the highlighter (if available)\n    pub(super) fn highlighter(&self) -> Option<&Rc<RefCell<Option<SyntaxHighlighter>>>> {\n        match self {\n            InputMode::CodeEditor { highlighter, .. } => Some(highlighter),\n            _ => None,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use ropey::Rope;\n\n    use crate::{\n        highlighter::DiagnosticSet,\n        input::{TabSize, mode::InputMode},\n    };\n\n    #[test]\n    fn test_code_editor() {\n        let mode = InputMode::code_editor(\"rust\");\n        assert_eq!(mode.is_code_editor(), true);\n        assert_eq!(mode.is_multi_line(), true);\n        assert_eq!(mode.is_single_line(), false);\n        assert_eq!(mode.line_number(), true);\n        assert_eq!(mode.has_indent_guides(), true);\n        assert_eq!(mode.max_rows(), usize::MAX);\n        assert_eq!(mode.min_rows(), 1);\n        assert_eq!(mode.is_folding(), true);\n\n        let mode = InputMode::CodeEditor {\n            multi_line: false,\n            line_number: true,\n            indent_guides: true,\n            folding: true,\n            rows: 0,\n            tab: Default::default(),\n            language: \"rust\".into(),\n            highlighter: Default::default(),\n            diagnostics: DiagnosticSet::new(&Rope::new()),\n            parse_task: Default::default(),\n        };\n        assert_eq!(mode.is_code_editor(), true);\n        assert_eq!(mode.is_multi_line(), false);\n        assert_eq!(mode.is_single_line(), true);\n        assert_eq!(mode.line_number(), false);\n        assert_eq!(mode.has_indent_guides(), false);\n        assert_eq!(mode.max_rows(), 1);\n        assert_eq!(mode.min_rows(), 1);\n        assert_eq!(mode.is_folding(), false);\n    }\n\n    #[test]\n    fn test_plain() {\n        let mode = InputMode::PlainText {\n            multi_line: true,\n            tab: TabSize::default(),\n            rows: 5,\n        };\n        assert_eq!(mode.is_code_editor(), false);\n        assert_eq!(mode.is_multi_line(), true);\n        assert_eq!(mode.is_single_line(), false);\n        assert_eq!(mode.line_number(), false);\n        assert_eq!(mode.rows(), 5);\n        assert_eq!(mode.max_rows(), usize::MAX);\n        assert_eq!(mode.min_rows(), 1);\n\n        let mode = InputMode::plain_text();\n        assert_eq!(mode.is_code_editor(), false);\n        assert_eq!(mode.is_multi_line(), false);\n        assert_eq!(mode.is_single_line(), true);\n        assert_eq!(mode.line_number(), false);\n        assert_eq!(mode.max_rows(), 1);\n        assert_eq!(mode.min_rows(), 1);\n    }\n\n    #[test]\n    fn test_auto_grow() {\n        let mut mode = InputMode::auto_grow(2, 5);\n        assert_eq!(mode.is_code_editor(), false);\n        assert_eq!(mode.is_multi_line(), true);\n        assert_eq!(mode.is_single_line(), false);\n        assert_eq!(mode.line_number(), false);\n        assert_eq!(mode.rows(), 2);\n        assert_eq!(mode.max_rows(), 5);\n        assert_eq!(mode.min_rows(), 2);\n\n        mode.set_rows(4);\n        assert_eq!(mode.rows(), 4);\n\n        mode.set_rows(1);\n        assert_eq!(mode.rows(), 2);\n\n        mode.set_rows(10);\n        assert_eq!(mode.rows(), 5);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/movement.rs",
    "content": "use gpui::{Context, Point, Window};\n\nuse crate::input::{\n    InputState, MoveDown, MoveEnd, MoveHome, MoveLeft, MovePageDown, MovePageUp, MoveRight,\n    MoveToEnd, MoveToNextWord, MoveToPreviousWord, MoveToStart, MoveUp, RopeExt as _,\n};\n\n#[derive(Clone, Copy, PartialEq, Eq)]\npub(crate) enum MoveDirection {\n    Up,\n    Down,\n}\n\nimpl InputState {\n    /// Called after moving the cursor. Updates preferred_column if we know where the cursor now is.\n    pub(super) fn update_preferred_column(&mut self) {\n        let Some(last_layout) = &self.last_layout else {\n            self.preferred_column = None;\n            return;\n        };\n\n        let point = self.text.offset_to_point(self.cursor());\n        let Some(line) = last_layout.line(point.row) else {\n            self.preferred_column = None;\n            return;\n        };\n\n        let Some(pos) = line.position_for_index(point.column, last_layout) else {\n            self.preferred_column = None;\n            return;\n        };\n\n        self.preferred_column = Some((pos.x, point.column));\n    }\n\n    /// Move the cursor to the given offset.\n    ///\n    /// The offset is the UTF-8 offset.\n    ///\n    /// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.\n    pub(crate) fn move_to(\n        &mut self,\n        offset: usize,\n        direction: Option<MoveDirection>,\n        cx: &mut Context<Self>,\n    ) {\n        let offset = offset.clamp(0, self.text.len());\n        self.selected_range = (offset..offset).into();\n        self.scroll_to(offset, direction, cx);\n        self.pause_blink_cursor(cx);\n        self.update_preferred_column();\n        self.hide_context_menu(cx);\n        self.clear_inline_completion(cx);\n        cx.notify()\n    }\n\n    /// Move the cursor vertically by one line (up or down) while preserving the column if possible.\n    ///\n    /// move_lines: Number of lines to move vertically (positive for down, negative for up).\n    pub(super) fn move_vertical(\n        &mut self,\n        move_lines: isize,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if self.mode.is_single_line() {\n            return;\n        }\n        let Some(last_layout) = &self.last_layout else {\n            return;\n        };\n\n        let offset = self.cursor();\n        let was_preferred_column = self.preferred_column;\n\n        let mut display_point = self.display_map.offset_to_wrap_display_point(offset);\n\n        // Convert wrap row → display row (skips folded rows), move, then convert back\n        let current_display_row = self\n            .display_map\n            .wrap_row_to_display_row(display_point.row)\n            .unwrap_or_else(|| {\n                self.display_map\n                    .nearest_visible_display_row(display_point.row)\n            });\n        let max_display_row = self.display_map.display_row_count().saturating_sub(1);\n        let target_display_row = current_display_row\n            .saturating_add_signed(move_lines)\n            .min(max_display_row);\n        let target_wrap_row = self\n            .display_map\n            .display_row_to_wrap_row(target_display_row)\n            .unwrap_or(display_point.row);\n\n        display_point.row = target_wrap_row;\n        display_point.column = 0;\n        let mut new_offset = self.display_map.wrap_display_point_to_offset(display_point);\n\n        if let Some((preferred_x, column)) = was_preferred_column {\n            // Get display point again to update local_row.\n            let mut next_display_point = self.display_map.offset_to_wrap_display_point(new_offset);\n            next_display_point.column = 0;\n            let next_point = self\n                .display_map\n                .wrap_display_point_to_point(next_display_point);\n            let line_start_offset = self.text.line_start_offset(next_point.row);\n\n            // If in visible range, prefer to use position to get column.\n            if let Some(line) = last_layout.line(next_point.row) {\n                if let Some(x) = line.closest_index_for_position(\n                    Point {\n                        x: preferred_x,\n                        y: next_display_point.local_row * last_layout.line_height,\n                    },\n                    last_layout,\n                ) {\n                    new_offset = line_start_offset + x;\n                }\n            } else {\n                // Not in visible range, use column directly.\n                let max_line_len = self.text.slice_line(next_point.row).len();\n                new_offset = line_start_offset + column.min(max_line_len);\n            }\n        }\n\n        self.pause_blink_cursor(cx);\n        let direction = if move_lines < 0 {\n            MoveDirection::Up\n        } else {\n            MoveDirection::Down\n        };\n        self.move_to(new_offset, Some(direction), cx);\n        // Set back the preferred_column\n        self.preferred_column = was_preferred_column;\n        cx.notify();\n    }\n\n    pub(super) fn left(&mut self, _: &MoveLeft, _: &mut Window, cx: &mut Context<Self>) {\n        self.pause_blink_cursor(cx);\n        if self.selected_range.is_empty() {\n            self.move_to(self.previous_boundary(self.cursor()), None, cx);\n        } else {\n            self.move_to(self.selected_range.start, None, cx)\n        }\n    }\n\n    pub(super) fn right(&mut self, _: &MoveRight, _: &mut Window, cx: &mut Context<Self>) {\n        self.pause_blink_cursor(cx);\n        if self.selected_range.is_empty() {\n            self.move_to(self.next_boundary(self.selected_range.end), None, cx);\n        } else {\n            self.move_to(self.selected_range.end, None, cx)\n        }\n    }\n\n    pub(super) fn up(&mut self, action: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {\n        if self.handle_action_for_context_menu(Box::new(action.clone()), window, cx) {\n            return;\n        }\n\n        if self.mode.is_single_line() {\n            return;\n        }\n\n        if !self.selected_range.is_empty() {\n            self.move_to(\n                self.previous_boundary(self.selected_range.start.saturating_sub(1)),\n                Some(MoveDirection::Up),\n                cx,\n            );\n        }\n        self.pause_blink_cursor(cx);\n        self.move_vertical(-1, window, cx);\n    }\n\n    pub(super) fn down(&mut self, action: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {\n        if self.handle_action_for_context_menu(Box::new(action.clone()), window, cx) {\n            return;\n        }\n\n        if self.mode.is_single_line() {\n            return;\n        }\n\n        if !self.selected_range.is_empty() {\n            self.move_to(\n                self.next_boundary(self.selected_range.end.saturating_sub(1)),\n                Some(MoveDirection::Down),\n                cx,\n            );\n        }\n\n        self.pause_blink_cursor(cx);\n        self.move_vertical(1, window, cx);\n    }\n\n    pub(super) fn page_up(&mut self, _: &MovePageUp, window: &mut Window, cx: &mut Context<Self>) {\n        if self.mode.is_single_line() {\n            return;\n        }\n\n        let Some(last_layout) = &self.last_layout else {\n            return;\n        };\n\n        let display_lines = (self.input_bounds.size.height / last_layout.line_height) as isize;\n        self.move_vertical(-display_lines, window, cx);\n    }\n\n    pub(super) fn page_down(\n        &mut self,\n        _: &MovePageDown,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if self.mode.is_single_line() {\n            return;\n        }\n\n        let Some(last_layout) = &self.last_layout else {\n            return;\n        };\n\n        let display_lines = (self.input_bounds.size.height / last_layout.line_height) as isize;\n        self.move_vertical(display_lines, window, cx);\n    }\n\n    pub(super) fn home(&mut self, _: &MoveHome, _: &mut Window, cx: &mut Context<Self>) {\n        self.pause_blink_cursor(cx);\n        let offset = self.start_of_line();\n        self.move_to(offset, Some(MoveDirection::Up), cx);\n    }\n\n    pub(super) fn end(&mut self, _: &MoveEnd, _: &mut Window, cx: &mut Context<Self>) {\n        self.pause_blink_cursor(cx);\n        let offset = self.end_of_line();\n        self.move_to(offset, Some(MoveDirection::Down), cx);\n    }\n\n    pub(super) fn move_to_start(\n        &mut self,\n        _: &MoveToStart,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.move_to(0, None, cx);\n    }\n\n    pub(super) fn move_to_end(&mut self, _: &MoveToEnd, _: &mut Window, cx: &mut Context<Self>) {\n        self.move_to(self.text.len(), None, cx);\n    }\n\n    pub(super) fn move_to_previous_word(\n        &mut self,\n        _: &MoveToPreviousWord,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let offset = self.previous_start_of_word();\n        self.move_to(offset, None, cx);\n    }\n\n    pub(super) fn move_to_next_word(\n        &mut self,\n        _: &MoveToNextWord,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let offset = self.next_end_of_word();\n        self.move_to(offset, None, cx);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/number_input.rs",
    "content": "use gpui::{\n    AnyElement, App, Context, Corners, Edges, Entity, EventEmitter, FocusHandle, Focusable,\n    InteractiveElement, IntoElement, KeyBinding, ParentElement, RenderOnce, SharedString,\n    StyleRefinement, Styled, TextAlign, Window, actions, prelude::FluentBuilder as _,\n};\n\nuse crate::{\n    ActiveTheme, Disableable, IconName, Sizable, Size, StyledExt as _, button::Button, h_flex,\n};\n\nuse super::{Input, InputState};\n\nactions!(number_input, [Increment, Decrement]);\n\nconst CONTEXT: &str = \"NumberInput\";\npub fn init(cx: &mut App) {\n    cx.bind_keys(vec![\n        KeyBinding::new(\"up\", Increment, Some(CONTEXT)),\n        KeyBinding::new(\"down\", Decrement, Some(CONTEXT)),\n    ]);\n}\n\n/// A number input element with increment and decrement buttons.\n#[derive(IntoElement)]\npub struct NumberInput {\n    state: Entity<InputState>,\n    placeholder: SharedString,\n    size: Size,\n    prefix: Option<AnyElement>,\n    suffix: Option<AnyElement>,\n    appearance: bool,\n    disabled: bool,\n    style: StyleRefinement,\n}\n\nimpl NumberInput {\n    /// Create a new [`NumberInput`] element bind to the [`InputState`].\n    pub fn new(state: &Entity<InputState>) -> Self {\n        Self {\n            state: state.clone(),\n            size: Size::default(),\n            placeholder: SharedString::default(),\n            prefix: None,\n            suffix: None,\n            appearance: true,\n            disabled: false,\n            style: StyleRefinement::default(),\n        }\n    }\n\n    /// Set the placeholder text of the number input.\n    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {\n        self.placeholder = placeholder.into();\n        self\n    }\n\n    /// Set the prefix element of the number input.\n    pub fn prefix(mut self, prefix: impl IntoElement) -> Self {\n        self.prefix = Some(prefix.into_any_element());\n        self\n    }\n\n    /// Set the suffix element of the number input.\n    pub fn suffix(mut self, suffix: impl IntoElement) -> Self {\n        self.suffix = Some(suffix.into_any_element());\n        self\n    }\n\n    /// Set the appearance of the number input, if false will no border and background.\n    pub fn appearance(mut self, appearance: bool) -> Self {\n        self.appearance = appearance;\n        self\n    }\n\n    fn on_increment(state: &Entity<InputState>, window: &mut Window, cx: &mut App) {\n        state.update(cx, |state, cx| {\n            state.focus(window, cx);\n            state.on_action_increment(&Increment, window, cx);\n        })\n    }\n\n    fn on_decrement(state: &Entity<InputState>, window: &mut Window, cx: &mut App) {\n        state.update(cx, |state, cx| {\n            state.focus(window, cx);\n            state.on_action_decrement(&Decrement, window, cx);\n        })\n    }\n}\n\nimpl Disableable for NumberInput {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl InputState {\n    fn on_action_increment(&mut self, _: &Increment, window: &mut Window, cx: &mut Context<Self>) {\n        self.on_number_input_step(StepAction::Increment, window, cx);\n    }\n\n    fn on_action_decrement(&mut self, _: &Decrement, window: &mut Window, cx: &mut Context<Self>) {\n        self.on_number_input_step(StepAction::Decrement, window, cx);\n    }\n\n    fn on_number_input_step(&mut self, action: StepAction, _: &mut Window, cx: &mut Context<Self>) {\n        if self.disabled {\n            return;\n        }\n\n        cx.emit(NumberInputEvent::Step(action));\n    }\n}\n\n#[derive(Clone, Copy, PartialEq, Eq)]\npub enum StepAction {\n    Decrement,\n    Increment,\n}\npub enum NumberInputEvent {\n    Step(StepAction),\n}\nimpl EventEmitter<NumberInputEvent> for InputState {}\n\nimpl Focusable for NumberInput {\n    fn focus_handle(&self, cx: &App) -> FocusHandle {\n        self.state.focus_handle(cx)\n    }\n}\n\nimpl Sizable for NumberInput {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Styled for NumberInput {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for NumberInput {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        h_flex()\n            .id((\"number-input\", self.state.entity_id()))\n            .key_context(CONTEXT)\n            .on_action(window.listener_for(&self.state, InputState::on_action_increment))\n            .on_action(window.listener_for(&self.state, InputState::on_action_decrement))\n            .flex_1()\n            .rounded(cx.theme().radius)\n            .refine_style(&self.style)\n            .when(self.disabled, |this| this.opacity(0.5))\n            .child(\n                Button::new(\"minus\")\n                    .outline()\n                    .with_size(self.size)\n                    .icon(IconName::Minus)\n                    .compact()\n                    .tab_stop(false)\n                    .disabled(self.disabled)\n                    .border_color(cx.theme().input)\n                    .border_corners(Corners {\n                        top_left: true,\n                        top_right: false,\n                        bottom_right: false,\n                        bottom_left: true,\n                    })\n                    .border_edges(Edges {\n                        top: self.appearance,\n                        right: false,\n                        bottom: self.appearance,\n                        left: self.appearance,\n                    })\n                    .on_click({\n                        let state = self.state.clone();\n                        move |_, window, cx| {\n                            Self::on_decrement(&state, window, cx);\n                        }\n                    }),\n            )\n            .child(\n                Input::new(&self.state)\n                    .appearance(self.appearance)\n                    .with_size(self.size)\n                    .disabled(self.disabled)\n                    .gap_0()\n                    .rounded_none()\n                    .text_align(TextAlign::Center)\n                    .when_some(self.prefix, |this, prefix| this.prefix(prefix))\n                    .when_some(self.suffix, |this, suffix| this.suffix(suffix)),\n            )\n            .child(\n                Button::new(\"plus\")\n                    .outline()\n                    .with_size(self.size)\n                    .icon(IconName::Plus)\n                    .compact()\n                    .tab_stop(false)\n                    .disabled(self.disabled)\n                    .border_color(cx.theme().input)\n                    .border_corners(Corners {\n                        top_left: false,\n                        top_right: true,\n                        bottom_right: true,\n                        bottom_left: false,\n                    })\n                    .border_edges(Edges {\n                        top: self.appearance,\n                        right: self.appearance,\n                        bottom: self.appearance,\n                        left: false,\n                    })\n                    .on_click({\n                        let state = self.state.clone();\n                        move |_, window, cx| {\n                            Self::on_increment(&state, window, cx);\n                        }\n                    }),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/otp_input.rs",
    "content": "use gpui::{\n    AnyElement, App, AppContext as _, Context, Empty, Entity, EventEmitter, FocusHandle, Focusable,\n    InteractiveElement, IntoElement, KeyDownEvent, MouseButton, MouseDownEvent, ParentElement as _,\n    Render, RenderOnce, SharedString, Styled as _, Subscription, Window, div,\n    prelude::FluentBuilder, px,\n};\n\nuse super::{InputEvent, blink_cursor::BlinkCursor, input::input_style};\nuse crate::{ActiveTheme, Disableable, Icon, IconName, Sizable, Size, h_flex, v_flex};\n\npub struct OtpState {\n    focus_handle: FocusHandle,\n    value: SharedString,\n    blink_cursor: Entity<BlinkCursor>,\n    masked: bool,\n    length: usize,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl OtpState {\n    /// Create a new [`OtpState`] with the specified length.\n    pub fn new(length: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let focus_handle = cx.focus_handle();\n        let blink_cursor = cx.new(|_| BlinkCursor::new());\n\n        let _subscriptions = vec![\n            // Observe the blink cursor to repaint the view when it changes.\n            cx.observe(&blink_cursor, |_, _, cx| cx.notify()),\n            // Blink the cursor when the window is active, pause when it's not.\n            cx.observe_window_activation(window, |this, window, cx| {\n                if window.is_window_active() {\n                    let focus_handle = this.focus_handle.clone();\n                    if focus_handle.is_focused(window) {\n                        this.blink_cursor.update(cx, |blink_cursor, cx| {\n                            blink_cursor.start(cx);\n                        });\n                    }\n                }\n            }),\n            cx.on_focus(&focus_handle, window, Self::on_focus),\n            cx.on_blur(&focus_handle, window, Self::on_blur),\n        ];\n\n        Self {\n            length,\n            focus_handle: focus_handle.clone(),\n            value: SharedString::default(),\n            blink_cursor: blink_cursor.clone(),\n            masked: false,\n            _subscriptions,\n        }\n    }\n\n    /// Set default value of the OTP Input.\n    pub fn default_value(mut self, value: impl Into<SharedString>) -> Self {\n        self.value = value.into();\n        self\n    }\n\n    /// Set value of the OTP Input.\n    pub fn set_value(\n        &mut self,\n        value: impl Into<SharedString>,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.value = value.into();\n        cx.notify();\n    }\n\n    /// Return the value of the OTP Input.\n    pub fn value(&self) -> &SharedString {\n        &self.value\n    }\n\n    /// Set masked to true use masked input.\n    pub fn masked(mut self, masked: bool) -> Self {\n        self.masked = masked;\n        self\n    }\n\n    /// Set masked to true use masked input.\n    pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.masked = masked;\n        cx.notify();\n    }\n\n    /// Focus the OTP Input.\n    pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {\n        self.focus_handle.focus(window, cx);\n    }\n\n    fn on_input_mouse_down(\n        &mut self,\n        _: &MouseDownEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        window.focus(&self.focus_handle, cx);\n    }\n\n    fn on_key_down(&mut self, event: &KeyDownEvent, window: &mut Window, cx: &mut Context<Self>) {\n        let mut chars: Vec<char> = self.value.chars().collect();\n        let ix = chars.len();\n\n        let key = event.keystroke.key.as_str();\n\n        match key {\n            \"backspace\" => {\n                if ix > 0 {\n                    let ix = ix - 1;\n                    chars.remove(ix);\n                }\n\n                window.prevent_default();\n                cx.stop_propagation();\n            }\n            _ => {\n                let c = key.chars().next().unwrap();\n                if !matches!(c, '0'..='9') {\n                    return;\n                }\n                if ix >= self.length {\n                    return;\n                }\n\n                chars.push(c);\n\n                window.prevent_default();\n                cx.stop_propagation();\n            }\n        }\n\n        self.pause_blink_cursor(cx);\n        self.value = SharedString::from(chars.iter().collect::<String>());\n\n        if self.value.chars().count() == self.length {\n            cx.emit(InputEvent::Change);\n        }\n        cx.notify()\n    }\n\n    fn on_focus(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        self.blink_cursor.update(cx, |cursor, cx| {\n            cursor.start(cx);\n        });\n        cx.emit(InputEvent::Focus);\n    }\n\n    fn on_blur(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        self.blink_cursor.update(cx, |cursor, cx| {\n            cursor.stop(cx);\n        });\n        cx.emit(InputEvent::Blur);\n    }\n\n    fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {\n        self.blink_cursor.update(cx, |cursor, cx| {\n            cursor.pause(cx);\n        });\n    }\n}\nimpl Focusable for OtpState {\n    fn focus_handle(&self, _: &gpui::App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\nimpl EventEmitter<InputEvent> for OtpState {}\nimpl Render for OtpState {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        Empty\n    }\n}\n\n/// A One Time Password (OTP) input element.\n///\n/// This can accept a fixed length number and can be masked.\n///\n/// Use case example:\n///\n/// - SMS OTP\n/// - Authenticator OTP\n#[derive(IntoElement)]\npub struct OtpInput {\n    state: Entity<OtpState>,\n    number_of_groups: usize,\n    size: Size,\n    disabled: bool,\n}\n\nimpl OtpInput {\n    /// Create a new [`OtpInput`] element bind to the [`OtpState`].\n    pub fn new(state: &Entity<OtpState>) -> Self {\n        Self {\n            state: state.clone(),\n            number_of_groups: 2,\n            size: Size::Medium,\n            disabled: false,\n        }\n    }\n\n    /// Set number of groups in the OTP Input.\n    pub fn groups(mut self, n: usize) -> Self {\n        self.number_of_groups = n;\n        self\n    }\n}\nimpl Disableable for OtpInput {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\nimpl Sizable for OtpInput {\n    fn with_size(mut self, size: impl Into<crate::Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\nimpl RenderOnce for OtpInput {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let state = self.state.read(cx);\n        let blink_show = state.blink_cursor.read(cx).visible();\n        let is_focused = state.focus_handle.is_focused(window);\n\n        let text_size = match self.size {\n            Size::XSmall => px(14.),\n            Size::Small => px(14.),\n            Size::Medium => px(16.),\n            Size::Large => px(18.),\n            Size::Size(v) => v * 0.5,\n        };\n\n        let cursor_ix = state\n            .value\n            .chars()\n            .count()\n            .min(state.length.saturating_sub(1));\n        let mut groups: Vec<Vec<AnyElement>> = Vec::with_capacity(self.number_of_groups);\n        let mut group_ix = 0;\n        let group_items_count = state.length / self.number_of_groups;\n        for _ in 0..self.number_of_groups {\n            groups.push(vec![]);\n        }\n\n        let (bg, fg) = input_style(self.disabled, cx);\n\n        for ix in 0..state.length {\n            let c = state.value.chars().nth(ix);\n            if ix % group_items_count == 0 && ix != 0 {\n                group_ix += 1;\n            }\n\n            let is_input_focused = ix == cursor_ix && is_focused;\n\n            groups[group_ix].push(\n                h_flex()\n                    .id(ix)\n                    .border_1()\n                    .border_color(cx.theme().input)\n                    .bg(bg)\n                    .text_color(fg)\n                    .when(self.disabled, |this| this.opacity(0.5))\n                    .when(is_input_focused, |this| this.border_color(cx.theme().ring))\n                    .when(cx.theme().shadow, |this| this.shadow_xs())\n                    .items_center()\n                    .justify_center()\n                    .rounded(cx.theme().radius)\n                    .text_size(text_size)\n                    .map(|this| match self.size {\n                        Size::XSmall => this.w_6().h_6(),\n                        Size::Small => this.w_6().h_6(),\n                        Size::Medium => this.w_8().h_8(),\n                        Size::Large => this.w_11().h_11(),\n                        Size::Size(px) => this.w(px).h(px),\n                    })\n                    .on_mouse_down(\n                        MouseButton::Left,\n                        window.listener_for(&self.state, OtpState::on_input_mouse_down),\n                    )\n                    .map(|this| match c {\n                        Some(c) => {\n                            if state.masked {\n                                this.child(\n                                    Icon::new(IconName::Asterisk)\n                                        .text_color(cx.theme().secondary_foreground)\n                                        .when(self.disabled, |this| {\n                                            this.text_color(cx.theme().muted_foreground)\n                                        })\n                                        .with_size(text_size),\n                                )\n                            } else {\n                                this.child(c.to_string())\n                            }\n                        }\n                        None => this.when(is_input_focused && blink_show, |this| {\n                            this.child(\n                                div()\n                                    .h_4()\n                                    .w_0()\n                                    .border_l_3()\n                                    .border_color(cx.theme().caret),\n                            )\n                        }),\n                    })\n                    .into_any_element(),\n            );\n        }\n\n        v_flex()\n            .id((\"otp-input\", self.state.entity_id()))\n            .track_focus(&self.state.read(cx).focus_handle)\n            .when(!self.disabled, |this| {\n                this.on_key_down(window.listener_for(&self.state, OtpState::on_key_down))\n            })\n            .items_center()\n            .child(\n                h_flex().items_center().gap_5().children(\n                    groups\n                        .into_iter()\n                        .map(|inputs| h_flex().items_center().gap_1().children(inputs)),\n                ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/popovers/code_action_menu.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{\n    Action, AnyElement, App, AppContext, Context, DismissEvent, Empty, Entity, EventEmitter,\n    Half as _, InteractiveElement as _, IntoElement, ParentElement, Pixels, Point, Render,\n    RenderOnce, SharedString, Styled, StyledText, Subscription, Window, deferred, div,\n    prelude::FluentBuilder, px, relative,\n};\nuse lsp_types::CodeAction;\n\nconst MAX_MENU_WIDTH: Pixels = px(320.);\nconst MAX_MENU_HEIGHT: Pixels = px(480.);\n\nuse crate::{\n    ActiveTheme, IndexPath, Selectable, actions, h_flex,\n    input::{self, InputState, popovers::editor_popover},\n    list::{List, ListDelegate, ListEvent, ListState},\n};\n\n#[derive(Debug, Clone)]\npub(crate) struct CodeActionItem {\n    /// The `id` of the `CodeActionProvider` that provided this item.\n    pub(crate) provider_id: SharedString,\n    pub(crate) action: CodeAction,\n}\n\nstruct MenuDelegate {\n    menu: Entity<CodeActionMenu>,\n    items: Vec<Rc<CodeActionItem>>,\n    selected_ix: usize,\n}\n\nimpl MenuDelegate {\n    fn set_items(&mut self, items: Vec<CodeActionItem>) {\n        self.items = items.into_iter().map(Rc::new).collect();\n        self.selected_ix = 0;\n    }\n\n    fn selected_item(&self) -> Option<&Rc<CodeActionItem>> {\n        self.items.get(self.selected_ix)\n    }\n}\n\n#[derive(IntoElement)]\nstruct MenuItem {\n    ix: usize,\n    item: Rc<CodeActionItem>,\n    children: Vec<AnyElement>,\n    selected: bool,\n}\n\nimpl MenuItem {\n    fn new(ix: usize, item: Rc<CodeActionItem>) -> Self {\n        Self {\n            ix,\n            item,\n            children: vec![],\n            selected: false,\n        }\n    }\n}\nimpl Selectable for MenuItem {\n    fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        self.selected\n    }\n}\n\nimpl ParentElement for MenuItem {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\nimpl RenderOnce for MenuItem {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let item = self.item;\n\n        let highlights = vec![];\n\n        h_flex()\n            .id(self.ix)\n            .gap_2()\n            .p_1()\n            .text_xs()\n            .line_height(relative(1.))\n            .rounded(cx.theme().radius.half())\n            .hover(|this| this.bg(cx.theme().accent.opacity(0.8)))\n            .when(self.selected, |this| {\n                this.bg(cx.theme().accent)\n                    .text_color(cx.theme().accent_foreground)\n            })\n            .child(\n                div().child(StyledText::new(item.action.title.clone()).with_highlights(highlights)),\n            )\n            .children(self.children)\n    }\n}\n\nimpl EventEmitter<DismissEvent> for MenuDelegate {}\n\nimpl ListDelegate for MenuDelegate {\n    type Item = MenuItem;\n\n    fn items_count(&self, _: usize, _: &gpui::App) -> usize {\n        self.items.len()\n    }\n\n    fn render_item(\n        &mut self,\n        ix: crate::IndexPath,\n        _: &mut Window,\n        _: &mut Context<ListState<Self>>,\n    ) -> Option<Self::Item> {\n        let item = self.items.get(ix.row)?;\n        Some(MenuItem::new(ix.row, item.clone()))\n    }\n\n    fn set_selected_index(\n        &mut self,\n        ix: Option<crate::IndexPath>,\n        _: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) {\n        self.selected_ix = ix.map(|i| i.row).unwrap_or(0);\n        cx.notify();\n    }\n\n    fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<ListState<Self>>) {\n        let Some(item) = self.selected_item() else {\n            return;\n        };\n\n        self.menu.update(cx, |this, cx| {\n            this.select_item(&item, window, cx);\n        });\n    }\n}\n\n/// A context menu for code completions and code actions.\npub struct CodeActionMenu {\n    offset: usize,\n    state: Entity<InputState>,\n    list: Entity<ListState<MenuDelegate>>,\n    open: bool,\n\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl CodeActionMenu {\n    /// Creates a new `CompletionMenu` with the given offset and completion items.\n    ///\n    /// NOTE: This element should not call from InputState::new, unless that will stack overflow.\n    pub(crate) fn new(\n        state: Entity<InputState>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Entity<Self> {\n        cx.new(|cx| {\n            let view = cx.entity();\n            let menu = MenuDelegate {\n                menu: view,\n                items: vec![],\n                selected_ix: 0,\n            };\n\n            let list = cx.new(|cx| ListState::new(menu, window, cx));\n\n            let _subscriptions =\n                vec![\n                    cx.subscribe(&list, |this: &mut Self, _, ev: &ListEvent, cx| {\n                        match ev {\n                            ListEvent::Confirm(_) => {\n                                this.hide(cx);\n                            }\n                            _ => {}\n                        }\n                        cx.notify();\n                    }),\n                ];\n\n            Self {\n                offset: 0,\n                state,\n                list,\n                open: false,\n                _subscriptions,\n            }\n        })\n    }\n\n    fn select_item(&mut self, item: &CodeActionItem, window: &mut Window, cx: &mut Context<Self>) {\n        let state = self.state.clone();\n        let item = item.clone();\n\n        cx.spawn_in(window, {\n            async move |_, cx| {\n                state.update_in(cx, |state, window, cx| {\n                    state.perform_code_action(&item, window, cx);\n                })\n            }\n        })\n        .detach();\n\n        self.hide(cx);\n    }\n\n    pub(crate) fn handle_action(\n        &mut self,\n        action: Box<dyn Action>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> bool {\n        if !self.open {\n            return false;\n        }\n\n        cx.propagate();\n        if action.partial_eq(&input::Enter { secondary: false }) {\n            self.on_action_enter(window, cx);\n        } else if action.partial_eq(&input::Escape) {\n            self.on_action_escape(window, cx);\n        } else if action.partial_eq(&input::MoveUp) {\n            self.on_action_up(window, cx);\n        } else if action.partial_eq(&input::MoveDown) {\n            self.on_action_down(window, cx);\n        } else {\n            return false;\n        }\n\n        true\n    }\n\n    fn on_action_enter(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        let Some(item) = self.list.read(cx).delegate().selected_item().cloned() else {\n            return;\n        };\n        self.select_item(&item, window, cx);\n    }\n\n    fn on_action_escape(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        self.hide(cx);\n    }\n\n    fn on_action_up(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        self.list.update(cx, |this, cx| {\n            this.on_action_select_prev(&actions::SelectUp, window, cx)\n        });\n    }\n\n    fn on_action_down(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        self.list.update(cx, |this, cx| {\n            this.on_action_select_next(&actions::SelectDown, window, cx)\n        });\n    }\n\n    pub(crate) fn is_open(&self) -> bool {\n        self.open\n    }\n\n    /// Hide the completion menu and reset the trigger start offset.\n    pub(crate) fn hide(&mut self, cx: &mut Context<Self>) {\n        self.open = false;\n        cx.notify();\n    }\n\n    pub(crate) fn show(\n        &mut self,\n        offset: usize,\n        items: impl Into<Vec<CodeActionItem>>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let items = items.into();\n        self.offset = offset;\n        self.open = true;\n        self.list.update(cx, |this, cx| {\n            this.delegate_mut().set_items(items);\n            this.set_selected_index(Some(IndexPath::new(0)), window, cx);\n        });\n\n        cx.notify();\n    }\n\n    fn origin(&self, cx: &App) -> Option<Point<Pixels>> {\n        let state = self.state.read(cx);\n        let Some(last_layout) = state.last_layout.as_ref() else {\n            return None;\n        };\n        let Some(cursor_origin) = last_layout.cursor_bounds.map(|b| b.origin) else {\n            return None;\n        };\n\n        let scroll_origin = self.state.read(cx).scroll_handle.offset();\n\n        Some(\n            scroll_origin + cursor_origin - state.input_bounds.origin\n                + Point::new(-px(4.), last_layout.line_height + px(4.)),\n        )\n    }\n}\n\nimpl Render for CodeActionMenu {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        if !self.open {\n            return Empty.into_any_element();\n        }\n\n        if self.list.read(cx).delegate().items.is_empty() {\n            self.open = false;\n            return Empty.into_any_element();\n        }\n\n        let Some(pos) = self.origin(cx) else {\n            return Empty.into_any_element();\n        };\n\n        let max_width = MAX_MENU_WIDTH.min(window.bounds().size.width - pos.x);\n\n        deferred(\n            editor_popover(\"code-action-menu\", cx)\n                .absolute()\n                .left(pos.x)\n                .top(pos.y)\n                .max_w(max_width)\n                .min_w(px(120.))\n                .child(List::new(&self.list).max_h(MAX_MENU_HEIGHT))\n                .on_mouse_down_out(cx.listener(|this, _, _, cx| {\n                    this.hide(cx);\n                })),\n        )\n        .into_any_element()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/popovers/completion_menu.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{\n    Action, AnyElement, App, AppContext, Context, DismissEvent, Empty, Entity, EventEmitter,\n    Half as _, HighlightStyle, InteractiveElement as _, IntoElement, ParentElement, Pixels, Point,\n    Render, RenderOnce, SharedString, Styled, StyledText, Subscription, Window, deferred, div,\n    prelude::FluentBuilder, px, relative,\n};\nuse lsp_types::{CompletionItem, CompletionTextEdit};\n\nconst MAX_MENU_WIDTH: Pixels = px(320.);\nconst MAX_MENU_HEIGHT: Pixels = px(240.);\nconst POPOVER_GAP: Pixels = px(4.);\n\nuse crate::{\n    ActiveTheme, IndexPath, Selectable, actions, h_flex,\n    input::{\n        self, InputState, RopeExt,\n        popovers::{editor_popover, render_markdown},\n    },\n    label::Label,\n    list::{List, ListDelegate, ListEvent, ListState},\n};\n\nstruct ContextMenuDelegate {\n    query: SharedString,\n    menu: Entity<CompletionMenu>,\n    items: Vec<Rc<CompletionItem>>,\n    selected_ix: usize,\n}\n\nimpl ContextMenuDelegate {\n    fn set_items(&mut self, items: Vec<CompletionItem>) {\n        self.items = items.into_iter().map(Rc::new).collect();\n        self.selected_ix = 0;\n    }\n\n    fn selected_item(&self) -> Option<&Rc<CompletionItem>> {\n        self.items.get(self.selected_ix)\n    }\n}\n\n#[derive(IntoElement)]\nstruct CompletionMenuItem {\n    ix: usize,\n    item: Rc<CompletionItem>,\n    children: Vec<AnyElement>,\n    selected: bool,\n    highlight_prefix: SharedString,\n}\n\nimpl CompletionMenuItem {\n    fn new(ix: usize, item: Rc<CompletionItem>) -> Self {\n        Self {\n            ix,\n            item,\n            children: vec![],\n            selected: false,\n            highlight_prefix: \"\".into(),\n        }\n    }\n\n    fn highlight_prefix(mut self, s: impl Into<SharedString>) -> Self {\n        self.highlight_prefix = s.into();\n        self\n    }\n}\nimpl Selectable for CompletionMenuItem {\n    fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        self.selected\n    }\n}\n\nimpl ParentElement for CompletionMenuItem {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\nimpl RenderOnce for CompletionMenuItem {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let item = self.item;\n\n        let deprecated = item.deprecated.unwrap_or(false);\n        let matched_len = item\n            .filter_text\n            .as_ref()\n            .map(|s| s.len())\n            .unwrap_or(self.highlight_prefix.len())\n            .min(item.label.len());\n\n        let highlights = vec![(\n            0..matched_len,\n            HighlightStyle {\n                color: Some(cx.theme().blue),\n                ..Default::default()\n            },\n        )];\n\n        h_flex()\n            .id(self.ix)\n            .gap_2()\n            .p_1()\n            .text_xs()\n            .line_height(relative(1.))\n            .rounded(cx.theme().radius.half())\n            .when(item.deprecated.unwrap_or(false), |this| this.line_through())\n            .hover(|this| this.bg(cx.theme().accent.opacity(0.8)))\n            .when(self.selected, |this| {\n                this.bg(cx.theme().accent)\n                    .text_color(cx.theme().accent_foreground)\n            })\n            .child(div().child(StyledText::new(item.label.clone()).with_highlights(highlights)))\n            .when(item.detail.is_some(), |this| {\n                this.child(\n                    Label::new(item.detail.as_deref().unwrap_or(\"\").to_string())\n                        .text_color(cx.theme().muted_foreground)\n                        .when(deprecated, |this| this.line_through())\n                        .italic(),\n                )\n            })\n            .children(self.children)\n    }\n}\n\nimpl EventEmitter<DismissEvent> for ContextMenuDelegate {}\n\nimpl ListDelegate for ContextMenuDelegate {\n    type Item = CompletionMenuItem;\n\n    fn items_count(&self, _: usize, _: &gpui::App) -> usize {\n        self.items.len()\n    }\n\n    fn render_item(\n        &mut self,\n        ix: crate::IndexPath,\n        _: &mut Window,\n        _: &mut Context<ListState<Self>>,\n    ) -> Option<Self::Item> {\n        let item = self.items.get(ix.row)?;\n        Some(CompletionMenuItem::new(ix.row, item.clone()).highlight_prefix(self.query.clone()))\n    }\n\n    fn set_selected_index(\n        &mut self,\n        ix: Option<crate::IndexPath>,\n        _: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) {\n        self.selected_ix = ix.map(|i| i.row).unwrap_or(0);\n        cx.notify();\n    }\n\n    fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<ListState<Self>>) {\n        let Some(item) = self.selected_item() else {\n            return;\n        };\n\n        self.menu.update(cx, |this, cx| {\n            this.select_item(&item, window, cx);\n        });\n    }\n}\n\n/// A context menu for code completions and code actions.\npub struct CompletionMenu {\n    offset: usize,\n    editor: Entity<InputState>,\n    list: Entity<ListState<ContextMenuDelegate>>,\n    open: bool,\n\n    /// The offset of the first character that triggered the completion.\n    pub(crate) trigger_start_offset: Option<usize>,\n    query: SharedString,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl CompletionMenu {\n    /// Creates a new `CompletionMenu` with the given offset and completion items.\n    ///\n    /// NOTE: This element should not call from InputState::new, unless that will stack overflow.\n    pub(crate) fn new(\n        editor: Entity<InputState>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Entity<Self> {\n        cx.new(|cx| {\n            let view = cx.entity();\n            let menu = ContextMenuDelegate {\n                query: SharedString::default(),\n                menu: view,\n                items: vec![],\n                selected_ix: 0,\n            };\n\n            let list = cx.new(|cx| ListState::new(menu, window, cx));\n\n            let _subscriptions =\n                vec![\n                    cx.subscribe(&list, |this: &mut Self, _, ev: &ListEvent, cx| {\n                        match ev {\n                            ListEvent::Confirm(_) => {\n                                this.hide(cx);\n                            }\n                            _ => {}\n                        }\n                        cx.notify();\n                    }),\n                ];\n\n            Self {\n                offset: 0,\n                editor,\n                list,\n                open: false,\n                trigger_start_offset: None,\n                query: SharedString::default(),\n                _subscriptions,\n            }\n        })\n    }\n\n    fn select_item(&mut self, item: &CompletionItem, window: &mut Window, cx: &mut Context<Self>) {\n        let offset = self.offset;\n        let item = item.clone();\n        let mut range = self.trigger_start_offset.unwrap_or(self.offset)..self.offset;\n\n        let editor = self.editor.clone();\n\n        cx.spawn_in(window, async move |_, cx| {\n            editor.update_in(cx, |editor, window, cx| {\n                editor.completion_inserting = true;\n\n                let mut new_text = item.label.clone();\n                if let Some(text_edit) = item.text_edit.as_ref() {\n                    match text_edit {\n                        CompletionTextEdit::Edit(edit) => {\n                            new_text = edit.new_text.clone();\n                            range.start = editor.text.position_to_offset(&edit.range.start);\n                            range.end = editor.text.position_to_offset(&edit.range.end);\n                        }\n                        CompletionTextEdit::InsertAndReplace(edit) => {\n                            new_text = edit.new_text.clone();\n                            range.start = editor.text.position_to_offset(&edit.replace.start);\n                            range.end = editor.text.position_to_offset(&edit.replace.end);\n                        }\n                    }\n                } else if let Some(insert_text) = item.insert_text.clone() {\n                    new_text = insert_text;\n                    range = offset..offset;\n                }\n\n                editor.replace_text_in_range_silent(\n                    Some(editor.range_to_utf16(&range)),\n                    &new_text,\n                    window,\n                    cx,\n                );\n                editor.completion_inserting = false;\n                // FIXME: Input not get the focus\n                editor.focus(window, cx);\n            })\n        })\n        .detach();\n\n        self.hide(cx);\n    }\n\n    pub(crate) fn handle_action(\n        &mut self,\n        action: Box<dyn Action>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> bool {\n        if !self.open {\n            return false;\n        }\n\n        cx.propagate();\n        if action.partial_eq(&input::Enter { secondary: false }) {\n            self.on_action_enter(window, cx);\n        } else if action.partial_eq(&input::Escape) {\n            self.on_action_escape(window, cx);\n        } else if action.partial_eq(&input::MoveUp) {\n            self.on_action_up(window, cx);\n        } else if action.partial_eq(&input::MoveDown) {\n            self.on_action_down(window, cx);\n        } else {\n            return false;\n        }\n\n        true\n    }\n\n    fn on_action_enter(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        let Some(item) = self.list.read(cx).delegate().selected_item().cloned() else {\n            return;\n        };\n        self.select_item(&item, window, cx);\n    }\n\n    fn on_action_escape(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        self.hide(cx);\n    }\n\n    fn on_action_up(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        self.list.update(cx, |this, cx| {\n            this.on_action_select_prev(&actions::SelectUp, window, cx)\n        });\n    }\n\n    fn on_action_down(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        self.list.update(cx, |this, cx| {\n            this.on_action_select_next(&actions::SelectDown, window, cx)\n        });\n    }\n\n    pub(crate) fn is_open(&self) -> bool {\n        self.open\n    }\n\n    /// Hide the completion menu and reset the trigger start offset.\n    pub(crate) fn hide(&mut self, cx: &mut Context<Self>) {\n        self.open = false;\n        self.trigger_start_offset = None;\n        cx.notify();\n    }\n\n    /// Sets the trigger start offset if it is not already set.\n    pub(crate) fn update_query(&mut self, start_offset: usize, query: impl Into<SharedString>) {\n        if self.trigger_start_offset.is_none() {\n            self.trigger_start_offset = Some(start_offset);\n        }\n        self.query = query.into();\n    }\n\n    pub(crate) fn show(\n        &mut self,\n        offset: usize,\n        items: impl Into<Vec<CompletionItem>>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let items = items.into();\n        self.offset = offset;\n        self.open = true;\n        self.list.update(cx, |this, cx| {\n            let longest_ix = items\n                .iter()\n                .enumerate()\n                .max_by_key(|(_, item)| {\n                    item.label.len() + item.detail.as_ref().map(|d| d.len()).unwrap_or(0)\n                })\n                .map(|(ix, _)| ix)\n                .unwrap_or(0);\n\n            this.delegate_mut().query = self.query.clone();\n            this.delegate_mut().set_items(items);\n            this.set_selected_index(Some(IndexPath::new(0)), window, cx);\n            this.set_item_to_measure_index(IndexPath::new(longest_ix), window, cx);\n        });\n\n        cx.notify();\n    }\n\n    fn origin(&self, cx: &App) -> Option<Point<Pixels>> {\n        let editor = self.editor.read(cx);\n        let Some(last_layout) = editor.last_layout.as_ref() else {\n            return None;\n        };\n        let Some(cursor_origin) = last_layout.cursor_bounds.map(|b| b.origin) else {\n            return None;\n        };\n\n        let scroll_origin = self.editor.read(cx).scroll_handle.offset();\n\n        Some(\n            scroll_origin + cursor_origin - editor.input_bounds.origin\n                + Point::new(-px(4.), last_layout.line_height + px(4.)),\n        )\n    }\n}\n\nimpl Render for CompletionMenu {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        if !self.open {\n            return Empty.into_any_element();\n        }\n\n        if self.list.read(cx).delegate().items.is_empty() {\n            self.open = false;\n            return Empty.into_any_element();\n        }\n\n        let Some(pos) = self.origin(cx) else {\n            return Empty.into_any_element();\n        };\n\n        let selected_documentation = self\n            .list\n            .read(cx)\n            .delegate()\n            .selected_item()\n            .and_then(|item| item.documentation.clone());\n\n        let max_width = MAX_MENU_WIDTH.min(window.bounds().size.width - pos.x);\n        let abs_pos = self.editor.read(cx).input_bounds.origin + pos;\n        let vertical_layout =\n            abs_pos.x + MAX_MENU_WIDTH + POPOVER_GAP + MAX_MENU_WIDTH + POPOVER_GAP\n                > window.bounds().size.width;\n\n        deferred(\n            div()\n                .absolute()\n                .left(pos.x)\n                .top(pos.y)\n                .flex()\n                .flex_row()\n                .gap(POPOVER_GAP)\n                .items_start()\n                .when(vertical_layout, |this| this.flex_col())\n                .child(\n                    editor_popover(\"completion-menu\", cx)\n                        .max_w(max_width)\n                        .min_w(px(120.))\n                        .child(List::new(&self.list).max_h(MAX_MENU_HEIGHT)),\n                )\n                .when_some(selected_documentation, |this, documentation| {\n                    let mut doc = match documentation {\n                        lsp_types::Documentation::String(s) => s.clone(),\n                        lsp_types::Documentation::MarkupContent(mc) => mc.value.clone(),\n                    };\n                    if vertical_layout {\n                        doc = doc.split(\"\\n\").next().unwrap_or_default().to_string();\n                    }\n\n                    this.child(\n                        div().child(\n                            editor_popover(\"completion-menu\", cx)\n                                .w(MAX_MENU_WIDTH)\n                                .px_2()\n                                .child(render_markdown(\"doc\", doc, window, cx)),\n                        ),\n                    )\n                })\n                .on_mouse_down_out(cx.listener(|this, _, _, cx| {\n                    this.hide(cx);\n                })),\n        )\n        .into_any_element()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/popovers/context_menu.rs",
    "content": "use gpui::{\n    App, AppContext as _, Context, Corner, DismissEvent, Entity, IntoElement, MouseDownEvent,\n    ParentElement as _, Pixels, Point, Render, Styled, Subscription, Window, anchored, deferred,\n    div, prelude::FluentBuilder as _, px,\n};\nuse rust_i18n::t;\n\nuse crate::{\n    ActiveTheme as _,\n    global_state::GlobalState,\n    input::{self, InputState, popovers::ContextMenu},\n    menu::PopupMenu,\n};\n\n/// Context menu for mouse right clicks.\npub(crate) struct MouseContextMenu {\n    editor: Entity<InputState>,\n    menu: Entity<PopupMenu>,\n    mouse_position: Point<Pixels>,\n    open: bool,\n\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl InputState {\n    pub(crate) fn handle_right_click_menu(\n        &mut self,\n        event: &MouseDownEvent,\n        offset: usize,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        // Check if we are already in a deferred context (e.g., inside a Popover)\n        // If so, don't show the context menu to prevent double-deferred panic\n        if GlobalState::global(cx).is_in_deferred_context() {\n            return;\n        }\n\n        // Show Mouse context menu\n        if !self.selected_range.contains(offset) {\n            self.move_to(offset, None, cx);\n        }\n\n        self.context_menu = Some(ContextMenu::MouseContext(self.mouse_context_menu.clone()));\n\n        let is_code_editor = self.mode.is_code_editor();\n        if is_code_editor {\n            self.handle_hover_definition(offset, window, cx);\n        }\n\n        let is_enable = !self.disabled;\n        let has_goto_definition = is_enable && self.lsp.definition_provider.is_some();\n        let has_code_action = is_enable && !self.lsp.code_action_providers.is_empty();\n        let is_selected = !self.selected_range.is_empty();\n        let has_paste = is_enable && cx.read_from_clipboard().is_some();\n\n        let action_context = self.focus_handle.clone();\n        self.mouse_context_menu.update(cx, |this, cx| {\n            this.mouse_position = event.position;\n            this.menu.update(cx, |menu, cx| {\n                let new_menu = PopupMenu::new(cx)\n                    .when(is_code_editor, |m| {\n                        m.menu_with_enable(\n                            t!(\"Input.Go to Definition\"),\n                            Box::new(input::GoToDefinition),\n                            has_goto_definition,\n                        )\n                        .menu_with_enable(\n                            t!(\"Input.Show Code Actions\"),\n                            Box::new(input::ToggleCodeActions),\n                            has_code_action,\n                        )\n                        .separator()\n                    })\n                    .menu_with_enable(\n                        t!(\"Input.Cut\"),\n                        Box::new(input::Cut),\n                        is_enable && is_selected,\n                    )\n                    .menu_with_enable(t!(\"Input.Copy\"), Box::new(input::Copy), is_selected)\n                    .menu_with_enable(t!(\"Input.Paste\"), Box::new(input::Paste), has_paste)\n                    .separator()\n                    .menu(t!(\"Input.Select All\"), Box::new(input::SelectAll));\n\n                menu.menu_items = new_menu.menu_items;\n                menu.action_context = Some(action_context);\n                cx.notify();\n            });\n            cx.defer_in(window, |this, _, cx| {\n                this.open = true;\n                cx.notify();\n            });\n        });\n    }\n}\n\nimpl MouseContextMenu {\n    pub(crate) fn new(\n        editor: Entity<InputState>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Entity<Self> {\n        cx.new(|cx| {\n            let menu = cx.new(|cx| PopupMenu::new(cx).small());\n\n            let _subscriptions = vec![cx.subscribe_in(&menu, window, {\n                move |this: &mut Self, _, _: &DismissEvent, window, cx| {\n                    this.close(window, cx);\n                }\n            })];\n\n            Self {\n                editor,\n                menu,\n                mouse_position: Point::default(),\n                open: false,\n                _subscriptions,\n            }\n        })\n    }\n\n    #[inline]\n    pub(crate) fn is_open(&self) -> bool {\n        self.open\n    }\n\n    #[inline]\n    pub(crate) fn close(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        self.open = false;\n        self.editor.update(cx, |this, cx| {\n            this.focus(window, cx);\n        });\n    }\n}\n\nimpl Render for MouseContextMenu {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        if !self.open {\n            return div().into_any_element();\n        }\n\n        deferred(\n            anchored()\n                .snap_to_window_with_margin(px(8.))\n                .anchor(Corner::TopLeft)\n                .position(self.mouse_position)\n                .child(\n                    div()\n                        .font_family(cx.theme().font_family.clone())\n                        .cursor_default()\n                        .child(self.menu.clone()),\n                ),\n        )\n        .into_any_element()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/popovers/diagnostic_popover.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{\n    prelude::FluentBuilder as _, px, App, AppContext as _, Bounds, Context, Empty, Entity,\n    IntoElement, Pixels, Point, Render, Styled, Window,\n};\n\nuse crate::{\n    highlighter::DiagnosticEntry,\n    input::{\n        popovers::{render_markdown, Popover},\n        InputState,\n    },\n};\n\npub struct DiagnosticPopover {\n    state: Entity<InputState>,\n    pub(crate) diagnostic: Rc<DiagnosticEntry>,\n    bounds: Bounds<Pixels>,\n    open: bool,\n}\n\nimpl DiagnosticPopover {\n    pub fn new(\n        diagnostic: &DiagnosticEntry,\n        state: Entity<InputState>,\n        cx: &mut App,\n    ) -> Entity<Self> {\n        let diagnostic = Rc::new(diagnostic.clone());\n\n        cx.new(|_| Self {\n            diagnostic,\n            state,\n            bounds: Bounds::default(),\n            open: true,\n        })\n    }\n\n    pub(crate) fn show(&mut self, cx: &mut Context<Self>) {\n        self.open = true;\n        cx.notify();\n    }\n\n    pub(crate) fn hide(&mut self, cx: &mut Context<Self>) {\n        self.open = false;\n        cx.notify();\n    }\n\n    pub(crate) fn check_to_hide(&mut self, mouse_position: Point<Pixels>, cx: &mut Context<Self>) {\n        if !self.open {\n            return;\n        }\n\n        let padding = px(5.);\n        let bounds = Bounds {\n            origin: self.bounds.origin.map(|v| v - padding),\n            size: self.bounds.size.map(|v| v + padding * 2.),\n        };\n\n        if !bounds.contains(&mouse_position) {\n            self.hide(cx);\n        }\n    }\n}\n\nimpl Render for DiagnosticPopover {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        if !self.open {\n            return Empty.into_any_element();\n        }\n\n        let message = self.diagnostic.message.clone();\n\n        let (border, bg, fg) = (\n            self.diagnostic.severity.border(cx),\n            self.diagnostic.severity.bg(cx),\n            self.diagnostic.severity.fg(cx),\n        );\n\n        Popover::new(\n            \"diagnostic-popover\",\n            self.state.clone(),\n            self.diagnostic.range.clone(),\n            move |window, cx| render_markdown(\"message\", message.clone(), window, cx),\n        )\n        .when(!self.open, |this| this.invisible())\n        .px_1()\n        .py_0p5()\n        .bg(bg)\n        .text_color(fg)\n        .border_1()\n        .border_color(border)\n        .into_any_element()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/popovers/hover_popover.rs",
    "content": "use std::{ops::Range, rc::Rc};\n\nuse gpui::{\n    AnyElement, App, AppContext as _, AvailableSpace, Bounds, Element, ElementId, Entity,\n    InteractiveElement, IntoElement, MouseDownEvent, ParentElement as _, Pixels, Render,\n    StatefulInteractiveElement as _, StyleRefinement, Styled, Window, deferred, div, point,\n    prelude::FluentBuilder as _, px,\n};\n\nuse crate::{\n    StyledExt,\n    input::{InputState, popovers::render_markdown},\n};\n\npub struct HoverPopover {\n    editor: Entity<InputState>,\n    /// The symbol range byte of the hover trigger.\n    pub(crate) symbol_range: Range<usize>,\n    pub(crate) hover: Rc<lsp_types::Hover>,\n}\n\nimpl HoverPopover {\n    pub fn new(\n        editor: Entity<InputState>,\n        symbol_range: Range<usize>,\n        hover: &lsp_types::Hover,\n        cx: &mut App,\n    ) -> Entity<Self> {\n        let hover = Rc::new(hover.clone());\n\n        cx.new(|_| Self {\n            editor,\n            symbol_range,\n            hover,\n        })\n    }\n\n    pub(crate) fn is_same(&self, offset: usize) -> bool {\n        self.symbol_range.contains(&offset)\n    }\n}\n\nimpl Render for HoverPopover {\n    fn render(&mut self, _: &mut Window, _: &mut gpui::Context<Self>) -> impl IntoElement {\n        let contents = match self.hover.contents.clone() {\n            lsp_types::HoverContents::Scalar(scalar) => match scalar {\n                lsp_types::MarkedString::String(s) => s,\n                lsp_types::MarkedString::LanguageString(ls) => ls.value,\n            },\n            lsp_types::HoverContents::Array(arr) => arr\n                .into_iter()\n                .map(|item| match item {\n                    lsp_types::MarkedString::String(s) => s,\n                    lsp_types::MarkedString::LanguageString(ls) => ls.value,\n                })\n                .collect::<Vec<_>>()\n                .join(\"\\n\\n\"),\n            lsp_types::HoverContents::Markup(markup) => markup.value,\n        };\n\n        Popover::new(\n            \"hover-popover\",\n            self.editor.clone(),\n            self.symbol_range.clone(),\n            move |window, cx| render_markdown(\"message\", contents.clone(), window, cx),\n        )\n        .into_any_element()\n    }\n}\n\npub(crate) struct Popover {\n    id: ElementId,\n    style: StyleRefinement,\n    editor: Entity<InputState>,\n    range: Range<usize>,\n    width_limit: Range<Pixels>,\n    content_builder: Box<dyn Fn(&mut Window, &mut App) -> AnyElement>,\n}\n\nimpl Styled for Popover {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Popover {\n    pub fn new<F, E>(\n        id: impl Into<ElementId>,\n        editor: Entity<InputState>,\n        range: Range<usize>,\n        f: F,\n    ) -> Self\n    where\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n        E: IntoElement,\n    {\n        Self {\n            id: id.into(),\n            editor,\n            range,\n            style: StyleRefinement::default(),\n            width_limit: px(200.)..px(500.),\n            content_builder: Box::new(move |window, cx| (f)(window, cx).into_any_element()),\n        }\n    }\n\n    /// Get the bounds of the range in the editor, if it is visible.\n    fn trigger_bounds(&self, cx: &App) -> Option<Bounds<Pixels>> {\n        let editor = self.editor.read(cx);\n        let Some(last_layout) = editor.last_layout.as_ref() else {\n            return None;\n        };\n\n        let Some(last_bounds) = editor.last_bounds else {\n            return None;\n        };\n\n        let (_, _, start_pos) = editor.line_and_position_for_offset(self.range.start);\n        let (_, _, end_pos) = editor.line_and_position_for_offset(self.range.end);\n\n        let Some(start_pos) = start_pos else {\n            return None;\n        };\n        let Some(end_pos) = end_pos else {\n            return None;\n        };\n\n        Some(Bounds::from_corners(\n            last_bounds.origin + start_pos,\n            last_bounds.origin + end_pos + point(px(0.), last_layout.line_height),\n        ))\n    }\n}\n\nimpl IntoElement for Popover {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\npub(crate) struct PopoverLayoutState {\n    state: Entity<bool>,\n    bounds: Bounds<Pixels>,\n    element: Option<AnyElement>,\n}\n\nimpl Element for Popover {\n    type RequestLayoutState = PopoverLayoutState;\n    type PrepaintState = ();\n\n    fn id(&self) -> Option<ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        _: Option<&gpui::GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (gpui::LayoutId, Self::RequestLayoutState) {\n        let open_state = window.use_keyed_state(\"popover-open\", cx, |_, _| true);\n        let trigger_bounds = match self.trigger_bounds(cx) {\n            Some(bounds) => bounds,\n            None => {\n                return (\n                    div().into_any_element().request_layout(window, cx),\n                    PopoverLayoutState {\n                        bounds: Bounds::default(),\n                        element: None,\n                        state: open_state,\n                    },\n                );\n            }\n        };\n\n        let max_width = self\n            .width_limit\n            .end\n            .min(window.bounds().size.width - SNAP_TO_EDGE * 2)\n            .max(px(200.));\n        let max_height = (window.bounds().size.height - SNAP_TO_EDGE * 2).min(px(320.));\n\n        let is_open = *open_state.read(cx);\n\n        let mut popover = deferred(\n            div()\n                .id(\"hover-popover-content\")\n                .when(!is_open, |s| s.invisible())\n                .flex_none()\n                .occlude()\n                .p_1()\n                .text_xs()\n                .popover_style(cx)\n                .shadow_md()\n                .max_w(max_width)\n                .max_h(max_height)\n                .overflow_y_scroll()\n                .refine_style(&self.style)\n                .child((self.content_builder)(window, cx)),\n        )\n        .into_any_element();\n\n        let popover_size = popover.layout_as_root(AvailableSpace::min_size(), window, cx);\n        const SNAP_TO_EDGE: Pixels = px(8.);\n        let top_space = trigger_bounds.top() - SNAP_TO_EDGE;\n        let right_space = window.bounds().size.width - trigger_bounds.left() - SNAP_TO_EDGE;\n\n        let mut pos = point(\n            trigger_bounds.left(),\n            trigger_bounds.top() - popover_size.height,\n        );\n        if popover_size.height > top_space {\n            pos.y = trigger_bounds.bottom();\n        }\n        if popover_size.width > right_space {\n            pos.x = trigger_bounds.right() - popover_size.width;\n        }\n\n        let mut empty = div().into_any_element();\n        let layout_id = empty.request_layout(window, cx);\n        (\n            layout_id,\n            PopoverLayoutState {\n                bounds: Bounds {\n                    origin: pos,\n                    size: popover_size,\n                },\n                element: Some(popover),\n                state: open_state,\n            },\n        )\n    }\n\n    fn prepaint(\n        &mut self,\n        _: Option<&gpui::GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        _: Bounds<Pixels>,\n        request_layout: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::PrepaintState {\n        let bounds = request_layout.bounds;\n        let Some(popover) = request_layout.element.as_mut() else {\n            return;\n        };\n\n        window.with_absolute_element_offset(bounds.origin, |window| {\n            popover.prepaint(window, cx);\n        })\n    }\n\n    fn paint(\n        &mut self,\n        _: Option<&gpui::GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        _: Bounds<Pixels>,\n        request_layout: &mut Self::RequestLayoutState,\n        _: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        let bounds = request_layout.bounds;\n        let Some(popover) = request_layout.element.as_mut() else {\n            return;\n        };\n\n        popover.paint(window, cx);\n\n        let open_state = request_layout.state.clone();\n        // Mouse down out to hide.\n        window.on_mouse_event(move |event: &MouseDownEvent, _, _, cx| {\n            if !bounds.contains(&event.position) {\n                open_state.update(cx, |open, cx| {\n                    *open = false;\n                    cx.notify();\n                })\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/popovers/mod.rs",
    "content": "mod code_action_menu;\nmod completion_menu;\nmod context_menu;\nmod diagnostic_popover;\nmod hover_popover;\n\npub(crate) use code_action_menu::*;\npub(crate) use completion_menu::*;\npub(crate) use context_menu::*;\npub(crate) use diagnostic_popover::*;\npub(crate) use hover_popover::*;\n\nuse gpui::{\n    App, Div, ElementId, Entity, InteractiveElement as _, IntoElement, SharedString, Stateful,\n    StyleRefinement, Styled as _, Window, div, px, rems,\n};\n\nuse crate::{\n    ActiveTheme, StyledExt as _,\n    text::{TextView, TextViewStyle},\n};\n\npub(crate) enum ContextMenu {\n    Completion(Entity<CompletionMenu>),\n    CodeAction(Entity<CodeActionMenu>),\n    MouseContext(Entity<MouseContextMenu>),\n}\n\nimpl ContextMenu {\n    pub(crate) fn is_open(&self, cx: &App) -> bool {\n        match self {\n            ContextMenu::Completion(menu) => menu.read(cx).is_open(),\n            ContextMenu::CodeAction(menu) => menu.read(cx).is_open(),\n            ContextMenu::MouseContext(menu) => menu.read(cx).is_open(),\n        }\n    }\n\n    pub(crate) fn render(&self) -> impl IntoElement {\n        match self {\n            ContextMenu::Completion(menu) => menu.clone().into_any_element(),\n            ContextMenu::CodeAction(menu) => menu.clone().into_any_element(),\n            ContextMenu::MouseContext(menu) => menu.clone().into_any_element(),\n        }\n    }\n}\n\npub(super) fn render_markdown(\n    id: impl Into<ElementId>,\n    markdown: impl Into<SharedString>,\n    _: &mut Window,\n    cx: &mut App,\n) -> TextView {\n    TextView::markdown(id, markdown)\n        .style(\n            TextViewStyle::default()\n                .paragraph_gap(rems(0.5))\n                .heading_font_size(|level, rem_size| match level {\n                    1..=3 => rem_size * 1,\n                    4 => rem_size * 0.9,\n                    _ => rem_size * 0.8,\n                })\n                .code_block(\n                    StyleRefinement::default()\n                        .bg(cx.theme().transparent)\n                        .p_0()\n                        .text_size(px(11.)),\n                ),\n        )\n        .selectable(true)\n}\n\npub(super) fn editor_popover(id: impl Into<ElementId>, cx: &App) -> Stateful<Div> {\n    div()\n        .id(id)\n        .flex_none()\n        .occlude()\n        .popover_style(cx)\n        .shadow_md()\n        .text_xs()\n        .p_1()\n}\n"
  },
  {
    "path": "crates/ui/src/input/rope_ext.rs",
    "content": "use std::ops::Range;\n\nuse ropey::{LineType, Rope, RopeSlice};\nuse sum_tree::Bias;\n\n#[cfg(not(target_family = \"wasm\"))]\npub use tree_sitter::{InputEdit, Point};\n\n#[cfg(target_family = \"wasm\")]\n/// Stub type for tree-sitter Point on WASM (tree-sitter not available).\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct Point {\n    pub row: usize,\n    pub column: usize,\n}\n\n#[cfg(target_family = \"wasm\")]\nimpl Point {\n    pub fn new(row: usize, column: usize) -> Self {\n        Self { row, column }\n    }\n}\n\n#[cfg(target_family = \"wasm\")]\n/// Stub type for tree-sitter InputEdit on WASM (tree-sitter not available).\n#[derive(Debug, Clone, Copy)]\npub struct InputEdit {\n    pub start_byte: usize,\n    pub old_end_byte: usize,\n    pub new_end_byte: usize,\n    pub start_position: Point,\n    pub old_end_position: Point,\n    pub new_end_position: Point,\n}\n\nuse crate::input::Position;\n\n/// An iterator over the lines of a `Rope`.\npub struct RopeLines<'a> {\n    rope: &'a Rope,\n    row: usize,\n    end_row: usize,\n}\n\nimpl<'a> RopeLines<'a> {\n    /// Create a new `RopeLines` iterator.\n    pub fn new(rope: &'a Rope) -> Self {\n        let end_row = rope.lines_len();\n        Self {\n            row: 0,\n            end_row,\n            rope,\n        }\n    }\n}\nimpl<'a> Iterator for RopeLines<'a> {\n    type Item = RopeSlice<'a>;\n\n    #[inline]\n    fn next(&mut self) -> Option<Self::Item> {\n        if self.row >= self.end_row {\n            return None;\n        }\n\n        let line = self.rope.slice_line(self.row);\n        self.row += 1;\n        Some(line)\n    }\n\n    #[inline]\n    fn nth(&mut self, n: usize) -> Option<Self::Item> {\n        self.row = self.row.saturating_add(n);\n        self.next()\n    }\n\n    #[inline]\n    fn size_hint(&self) -> (usize, Option<usize>) {\n        let len = self.end_row - self.row;\n        (len, Some(len))\n    }\n}\n\nimpl std::iter::ExactSizeIterator for RopeLines<'_> {}\nimpl std::iter::FusedIterator for RopeLines<'_> {}\n\n/// An extension trait for [`Rope`] to provide additional utility methods.\npub trait RopeExt {\n    /// Start offset of the line at the given row (0-based) index.\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use gpui_component::{Rope, RopeExt};\n    ///\n    /// let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n    /// assert_eq!(rope.line_start_offset(0), 0);\n    /// assert_eq!(rope.line_start_offset(1), 6);\n    /// ```\n    fn line_start_offset(&self, row: usize) -> usize;\n\n    /// Line the end offset (including `\\n`) of the line at the given row (0-based) index.\n    ///\n    /// Return the end of the rope if the row is out of bounds.\n    ///\n    /// ```\n    /// use gpui_component::{Rope, RopeExt};\n    /// let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n    /// assert_eq!(rope.line_end_offset(0), 5); // \"Hello\\n\"\n    /// assert_eq!(rope.line_end_offset(1), 12); // \"World\\r\\n\"\n    /// ```\n    fn line_end_offset(&self, row: usize) -> usize;\n\n    /// Return a line slice at the given row (0-based) index. including `\\r` if present, but not `\\n`.\n    ///\n    /// ```\n    /// use gpui_component::{Rope, RopeExt};\n    /// let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n    /// assert_eq!(rope.slice_line(0).to_string(), \"Hello\");\n    /// assert_eq!(rope.slice_line(1).to_string(), \"World\\r\");\n    /// assert_eq!(rope.slice_line(2).to_string(), \"This is a test 中文\");\n    /// assert_eq!(rope.slice_line(6).to_string(), \"\"); // out of bounds\n    /// ```\n    fn slice_line(&self, row: usize) -> RopeSlice<'_>;\n\n    /// Return a slice of rows in the given range (0-based, end exclusive).\n    ///\n    /// If the range is out of bounds, it will be clamped to the valid range.\n    ///\n    /// ```\n    /// use gpui_component::{Rope, RopeExt};\n    /// let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n    /// assert_eq!(rope.slice_lines(0..2).to_string(), \"Hello\\nWorld\\r\");\n    /// assert_eq!(rope.slice_lines(1..3).to_string(), \"World\\r\\nThis is a test 中文\");\n    /// assert_eq!(rope.slice_lines(2..5).to_string(), \"This is a test 中文\\nRope\");\n    /// assert_eq!(rope.slice_lines(3..10).to_string(), \"Rope\");\n    /// assert_eq!(rope.slice_lines(5..10).to_string(), \"\"); // out of bounds\n    /// ```\n    fn slice_lines(&self, rows_range: Range<usize>) -> RopeSlice<'_>;\n\n    /// Return an iterator over all lines in the rope.\n    ///\n    /// Each line slice includes `\\r` if present, but not `\\n`.\n    ///\n    /// ```\n    /// use gpui_component::{Rope, RopeExt};\n    /// let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n    /// let lines: Vec<_> = rope.iter_lines().map(|r| r.to_string()).collect();\n    /// assert_eq!(lines, vec![\"Hello\", \"World\\r\", \"This is a test 中文\", \"Rope\"]);\n    /// ```\n    fn iter_lines(&self) -> RopeLines<'_>;\n\n    /// Return the number of lines in the rope.\n    ///\n    /// ```\n    /// use gpui_component::{Rope, RopeExt};\n    /// let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n    /// assert_eq!(rope.lines_len(), 4);\n    /// ```\n    fn lines_len(&self) -> usize;\n\n    /// Return the length of the row (0-based) in characters, including `\\r` if present, but not `\\n`.\n    ///\n    /// If the row is out of bounds, return 0.\n    ///\n    /// ```\n    /// use gpui_component::{Rope, RopeExt};\n    /// let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n    /// assert_eq!(rope.line_len(0), 5); // \"Hello\"\n    /// assert_eq!(rope.line_len(1), 6); // \"World\\r\"\n    /// assert_eq!(rope.line_len(2), 21); // \"This is a test 中文\"\n    /// assert_eq!(rope.line_len(4), 0); // out of bounds\n    /// ```\n    fn line_len(&self, row: usize) -> usize;\n\n    /// Replace the text in the given byte range with new text.\n    ///\n    /// # Panics\n    ///\n    /// - If the range is not on char boundary.\n    /// - If the range is out of bounds.\n    ///\n    /// ```\n    /// use gpui_component::{Rope, RopeExt};\n    /// let mut rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n    /// rope.replace(6..11, \"Universe\");\n    /// assert_eq!(rope.to_string(), \"Hello\\nUniverse\\r\\nThis is a test 中文\\nRope\");\n    /// ```\n    fn replace(&mut self, range: Range<usize>, new_text: &str);\n\n    /// Get char at the given offset (byte).\n    ///\n    /// - If the offset is in the middle of a multi-byte character will panic.\n    /// - If the offset is out of bounds, return None.\n    fn char_at(&self, offset: usize) -> Option<char>;\n\n    /// Get the byte offset from the given line, column [`Position`] (0-based).\n    ///\n    /// The column is in characters.\n    fn position_to_offset(&self, line_col: &Position) -> usize;\n\n    /// Get the line, column [`Position`] (0-based) from the given byte offset.\n    ///\n    /// The column is in characters.\n    fn offset_to_position(&self, offset: usize) -> Position;\n\n    /// Get point (row, column) from the given byte offset.\n    ///\n    /// The column is in bytes.\n    fn offset_to_point(&self, offset: usize) -> Point;\n\n    /// Get byte offset from the given point (row, column).\n    ///\n    /// The column is 0-based in bytes.\n    fn point_to_offset(&self, point: Point) -> usize;\n\n    /// Get the word byte range at the given byte offset (0-based).\n    fn word_range(&self, offset: usize) -> Option<Range<usize>>;\n\n    /// Get word at the given byte offset (0-based).\n    fn word_at(&self, offset: usize) -> String;\n\n    /// Convert offset in UTF-16 to byte offset (0-based).\n    ///\n    /// Runs in O(log N) time.\n    fn offset_utf16_to_offset(&self, offset_utf16: usize) -> usize;\n\n    /// Convert byte offset (0-based) to offset in UTF-16.\n    ///\n    /// Runs in O(log N) time.\n    fn offset_to_offset_utf16(&self, offset: usize) -> usize;\n\n    /// Get a clipped offset (avoid in a char boundary).\n    ///\n    /// - If Bias::Left and inside the char boundary, return the ix - 1;\n    /// - If Bias::Right and in inside char boundary, return the ix + 1;\n    /// - Otherwise return the ix.\n    ///\n    /// ```\n    /// use gpui_component::{Rope, RopeExt};\n    /// use sum_tree::Bias;\n    ///\n    /// let rope = Rope::from(\"Hello 中文🎉 test\\nRope\");\n    /// assert_eq!(rope.clip_offset(5, Bias::Left), 5);\n    /// // Inside multi-byte character '中' (3 bytes)\n    /// assert_eq!(rope.clip_offset(7, Bias::Left), 6);\n    /// assert_eq!(rope.clip_offset(7, Bias::Right), 9);\n    /// ```\n    fn clip_offset(&self, offset: usize, bias: Bias) -> usize;\n\n    /// Convert offset in characters to byte offset (0-based).\n    ///\n    /// Run in O(n) time.\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use gpui_component::{Rope, RopeExt};\n    /// let rope = Rope::from(\"a 中文🎉 test\\nRope\");\n    /// assert_eq!(rope.char_index_to_offset(0), 0);\n    /// assert_eq!(rope.char_index_to_offset(1), 1);\n    /// assert_eq!(rope.char_index_to_offset(3), \"a 中\".len());\n    /// assert_eq!(rope.char_index_to_offset(5), \"a 中文🎉\".len());\n    /// ```\n    fn char_index_to_offset(&self, char_index: usize) -> usize;\n\n    /// Convert byte offset (0-based) to offset in characters.\n    ///\n    /// Run in O(n) time.\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use gpui_component::{Rope, RopeExt};\n    /// let rope = Rope::from(\"a 中文🎉 test\\nRope\");\n    /// assert_eq!(rope.offset_to_char_index(0), 0);\n    /// assert_eq!(rope.offset_to_char_index(1), 1);\n    /// assert_eq!(rope.offset_to_char_index(3), 3);\n    /// assert_eq!(rope.offset_to_char_index(4), 3);\n    /// ```\n    fn offset_to_char_index(&self, offset: usize) -> usize;\n}\n\nimpl RopeExt for Rope {\n    fn slice_line(&self, row: usize) -> RopeSlice<'_> {\n        let total_lines = self.lines_len();\n        if row >= total_lines {\n            return self.slice(0..0);\n        }\n\n        let line = self.line(row, LineType::LF);\n        if line.len() > 0 {\n            let line_end = line.len() - 1;\n            if line.is_char_boundary(line_end) && line.char(line_end) == '\\n' {\n                return line.slice(..line_end);\n            }\n        }\n\n        line\n    }\n\n    fn slice_lines(&self, rows_range: Range<usize>) -> RopeSlice<'_> {\n        let start = self.line_start_offset(rows_range.start);\n        let end = self.line_end_offset(rows_range.end.saturating_sub(1));\n        self.slice(start..end)\n    }\n\n    fn iter_lines(&self) -> RopeLines<'_> {\n        RopeLines::new(&self)\n    }\n\n    fn line_len(&self, row: usize) -> usize {\n        self.slice_line(row).len()\n    }\n\n    fn line_start_offset(&self, row: usize) -> usize {\n        self.point_to_offset(Point::new(row, 0))\n    }\n\n    fn offset_to_point(&self, offset: usize) -> Point {\n        let offset = self.clip_offset(offset, Bias::Left);\n        let row = self.byte_to_line_idx(offset, LineType::LF);\n        let line_start = self.line_to_byte_idx(row, LineType::LF);\n        let column = offset.saturating_sub(line_start);\n        Point::new(row, column)\n    }\n\n    fn point_to_offset(&self, point: Point) -> usize {\n        if point.row >= self.lines_len() {\n            return self.len();\n        }\n\n        let line_start = self.line_to_byte_idx(point.row, LineType::LF);\n        line_start + point.column\n    }\n\n    fn position_to_offset(&self, pos: &Position) -> usize {\n        let line = self.slice_line(pos.line as usize);\n        self.line_start_offset(pos.line as usize)\n            + line\n                .chars()\n                .take(pos.character as usize)\n                .map(|c| c.len_utf8())\n                .sum::<usize>()\n    }\n\n    fn offset_to_position(&self, offset: usize) -> Position {\n        let point = self.offset_to_point(offset);\n        let line = self.slice_line(point.row);\n        let offset = line.utf16_to_byte_idx(line.byte_to_utf16_idx(point.column));\n        let character = line.slice(..offset).chars().count();\n        Position::new(point.row as u32, character as u32)\n    }\n\n    fn line_end_offset(&self, row: usize) -> usize {\n        if row > self.lines_len() {\n            return self.len();\n        }\n\n        self.line_start_offset(row) + self.line_len(row)\n    }\n\n    fn lines_len(&self) -> usize {\n        self.len_lines(LineType::LF)\n    }\n\n    fn char_at(&self, offset: usize) -> Option<char> {\n        if offset > self.len() {\n            return None;\n        }\n\n        self.get_char(offset).ok()\n    }\n\n    fn word_range(&self, offset: usize) -> Option<Range<usize>> {\n        if offset >= self.len() {\n            return None;\n        }\n\n        let mut left = String::new();\n        let offset = self.clip_offset(offset, Bias::Left);\n        for c in self.chars_at(offset).reversed() {\n            if c.is_alphanumeric() || c == '_' {\n                left.insert(0, c);\n            } else {\n                break;\n            }\n        }\n        let start = offset.saturating_sub(left.len());\n\n        let right = self\n            .chars_at(offset)\n            .take_while(|c| c.is_alphanumeric() || *c == '_')\n            .collect::<String>();\n\n        let end = offset + right.len();\n\n        if start == end { None } else { Some(start..end) }\n    }\n\n    fn word_at(&self, offset: usize) -> String {\n        if let Some(range) = self.word_range(offset) {\n            self.slice(range).to_string()\n        } else {\n            String::new()\n        }\n    }\n\n    #[inline]\n    fn offset_utf16_to_offset(&self, offset_utf16: usize) -> usize {\n        if offset_utf16 > self.len_utf16() {\n            return self.len();\n        }\n\n        self.utf16_to_byte_idx(offset_utf16)\n    }\n\n    #[inline]\n    fn offset_to_offset_utf16(&self, offset: usize) -> usize {\n        if offset > self.len() {\n            return self.len_utf16();\n        }\n\n        self.byte_to_utf16_idx(offset)\n    }\n\n    fn replace(&mut self, range: Range<usize>, new_text: &str) {\n        let range =\n            self.clip_offset(range.start, Bias::Left)..self.clip_offset(range.end, Bias::Right);\n        self.remove(range.clone());\n        self.insert(range.start, new_text);\n    }\n\n    fn clip_offset(&self, offset: usize, bias: Bias) -> usize {\n        if offset > self.len() {\n            return self.len();\n        }\n\n        if self.is_char_boundary(offset) {\n            return offset;\n        }\n\n        if bias == Bias::Left {\n            self.floor_char_boundary(offset)\n        } else {\n            self.ceil_char_boundary(offset)\n        }\n    }\n\n    fn char_index_to_offset(&self, char_offset: usize) -> usize {\n        self.chars().take(char_offset).map(|c| c.len_utf8()).sum()\n    }\n\n    fn offset_to_char_index(&self, offset: usize) -> usize {\n        let offset = self.clip_offset(offset, Bias::Right);\n        self.slice(..offset).chars().count()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use ropey::Rope;\n    use sum_tree::Bias;\n    use tree_sitter::Point;\n\n    use crate::{RopeExt, input::Position};\n\n    #[test]\n    fn test_slice_line() {\n        let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n        assert_eq!(rope.slice_line(0).to_string(), \"Hello\");\n        assert_eq!(rope.slice_line(1).to_string(), \"World\\r\");\n        assert_eq!(rope.slice_line(2).to_string(), \"This is a test 中文\");\n        assert_eq!(rope.slice_line(3).to_string(), \"Rope\");\n\n        // over bounds\n        assert_eq!(rope.slice_line(6).to_string(), \"\");\n\n        // only have \\r end\n        let rope = Rope::from(\"Hello\\r\");\n        assert_eq!(rope.slice_line(0).to_string(), \"Hello\\r\");\n        assert_eq!(rope.slice_line(1).to_string(), \"\");\n    }\n\n    #[test]\n    fn test_lines_len() {\n        let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n        assert_eq!(rope.lines_len(), 4);\n        let rope = Rope::from(\"\");\n        assert_eq!(rope.lines_len(), 1);\n        let rope = Rope::from(\"Single line\");\n        assert_eq!(rope.lines_len(), 1);\n\n        // only have \\r end\n        let rope = Rope::from(\"Hello\\r\");\n        assert_eq!(rope.lines_len(), 1);\n    }\n\n    #[test]\n    fn test_lines() {\n        let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\\r\");\n        let lines: Vec<_> = rope.iter_lines().map(|r| r.to_string()).collect();\n        assert_eq!(\n            lines,\n            vec![\"Hello\", \"World\\r\", \"This is a test 中文\", \"Rope\\r\"]\n        );\n    }\n\n    #[test]\n    fn test_eq() {\n        let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n        assert!(rope.eq(&Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\")));\n        assert!(!rope.eq(&Rope::from(\"Hello\\nWorld\")));\n\n        let rope1 = rope.clone();\n        assert!(rope.eq(&rope1));\n    }\n\n    #[test]\n    fn test_iter_lines() {\n        let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n        let lines: Vec<_> = rope\n            .iter_lines()\n            .skip(1)\n            .take(2)\n            .map(|r| r.to_string())\n            .collect();\n        assert_eq!(lines, vec![\"World\\r\", \"This is a test 中文\"]);\n    }\n\n    #[test]\n    fn test_line_start_end_offset() {\n        let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n        assert_eq!(rope.line_start_offset(0), 0);\n        assert_eq!(rope.line_end_offset(0), 5);\n\n        assert_eq!(rope.line_start_offset(1), 6);\n        assert_eq!(rope.line_end_offset(1), 12);\n\n        assert_eq!(rope.line_start_offset(2), 13);\n        assert_eq!(rope.line_end_offset(2), 34);\n\n        assert_eq!(rope.line_start_offset(3), 35);\n        assert_eq!(rope.line_end_offset(3), 39);\n\n        assert_eq!(rope.line_start_offset(4), 39);\n        assert_eq!(rope.line_end_offset(4), 39);\n    }\n\n    #[test]\n    fn test_line_column() {\n        let rope = Rope::from(\"a 中文🎉 test\\nRope\");\n        assert_eq!(rope.position_to_offset(&Position::new(0, 3)), \"a 中\".len());\n        assert_eq!(\n            rope.position_to_offset(&Position::new(0, 5)),\n            \"a 中文🎉\".len()\n        );\n        assert_eq!(\n            rope.position_to_offset(&Position::new(1, 1)),\n            \"a 中文🎉 test\\nR\".len()\n        );\n\n        assert_eq!(\n            rope.offset_to_position(\"a 中文🎉 test\\nR\".len()),\n            Position::new(1, 1)\n        );\n        assert_eq!(\n            rope.offset_to_position(\"a 中文🎉\".len()),\n            Position::new(0, 5)\n        );\n    }\n\n    #[test]\n    fn test_offset_to_point() {\n        let rope = Rope::from(\"a 中文🎉 test\\nRope\");\n        assert_eq!(rope.offset_to_point(0), Point::new(0, 0));\n        assert_eq!(rope.offset_to_point(1), Point::new(0, 1));\n        assert_eq!(rope.offset_to_point(\"a 中\".len()), Point::new(0, 5));\n        assert_eq!(rope.offset_to_point(\"a 中文🎉\".len()), Point::new(0, 12));\n        assert_eq!(\n            rope.offset_to_point(\"a 中文🎉 test\\nR\".len()),\n            Point::new(1, 1)\n        );\n    }\n\n    #[test]\n    fn test_point_to_offset() {\n        let rope = Rope::from(\"a 中文🎉 test\\nRope\");\n        assert_eq!(rope.point_to_offset(Point::new(0, 0)), 0);\n        assert_eq!(rope.point_to_offset(Point::new(0, 1)), 1);\n        assert_eq!(rope.point_to_offset(Point::new(0, 5)), \"a 中\".len());\n        assert_eq!(rope.point_to_offset(Point::new(0, 12)), \"a 中文🎉\".len());\n        assert_eq!(\n            rope.point_to_offset(Point::new(1, 1)),\n            \"a 中文🎉 test\\nR\".len()\n        );\n    }\n\n    #[test]\n    fn test_char_at() {\n        let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文🎉\\nRope\");\n        assert_eq!(rope.char_at(0), Some('H'));\n        assert_eq!(rope.char_at(5), Some('\\n'));\n        assert_eq!(rope.char_at(13), Some('T'));\n        assert_eq!(rope.char_at(28), Some('中'));\n        assert_eq!(rope.char_at(34), Some('🎉'));\n        assert_eq!(rope.char_at(38), Some('\\n'));\n        assert_eq!(rope.char_at(50), None);\n    }\n\n    #[test]\n    fn test_word_at() {\n        let rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文 世界\\nRope\");\n        assert_eq!(rope.word_at(0), \"Hello\");\n        assert_eq!(rope.word_range(0), Some(0..5));\n        assert_eq!(rope.word_at(8), \"World\");\n        assert_eq!(rope.word_range(8), Some(6..11));\n        assert_eq!(rope.word_at(12), \"\");\n        assert_eq!(rope.word_range(12), None);\n        assert_eq!(rope.word_at(13), \"This\");\n        assert_eq!(rope.word_range(13), Some(13..17));\n        assert_eq!(rope.word_at(31), \"中文\");\n        assert_eq!(rope.word_range(31), Some(28..34));\n        assert_eq!(rope.word_at(38), \"世界\");\n        assert_eq!(rope.word_range(38), Some(35..41));\n        assert_eq!(rope.word_at(44), \"Rope\");\n        assert_eq!(rope.word_range(44), Some(42..46));\n        assert_eq!(rope.word_at(45), \"Rope\");\n    }\n\n    #[test]\n    fn test_offset_utf16_conversion() {\n        let rope = Rope::from(\"hello 中文🎉 test\\nRope\");\n        assert_eq!(rope.offset_to_offset_utf16(\"hello\".len()), 5);\n        assert_eq!(rope.offset_to_offset_utf16(\"hello 中\".len()), 7);\n        assert_eq!(rope.offset_to_offset_utf16(\"hello 中文\".len()), 8);\n        assert_eq!(rope.offset_to_offset_utf16(\"hello 中文🎉\".len()), 10);\n        assert_eq!(rope.offset_to_offset_utf16(100), 20);\n\n        assert_eq!(rope.offset_utf16_to_offset(5), \"hello\".len());\n        assert_eq!(rope.offset_utf16_to_offset(7), \"hello 中\".len());\n        assert_eq!(rope.offset_utf16_to_offset(8), \"hello 中文\".len());\n        assert_eq!(rope.offset_utf16_to_offset(10), \"hello 中文🎉\".len());\n        assert_eq!(rope.offset_utf16_to_offset(100), rope.len());\n    }\n\n    #[test]\n    fn test_replace() {\n        let mut rope = Rope::from(\"Hello\\nWorld\\r\\nThis is a test 中文\\nRope\");\n        rope.replace(6..11, \"Universe\");\n        assert_eq!(\n            rope.to_string(),\n            \"Hello\\nUniverse\\r\\nThis is a test 中文\\nRope\"\n        );\n\n        rope.replace(0..5, \"Hi\");\n        assert_eq!(\n            rope.to_string(),\n            \"Hi\\nUniverse\\r\\nThis is a test 中文\\nRope\"\n        );\n\n        rope.replace(rope.len() - 4..rope.len(), \"String\");\n        assert_eq!(\n            rope.to_string(),\n            \"Hi\\nUniverse\\r\\nThis is a test 中文\\nString\"\n        );\n\n        // Test for not on a char boundary\n        let mut rope = Rope::from(\"中文\");\n        rope.replace(0..1, \"New\");\n        // autocorrect-disable\n        assert_eq!(rope.to_string(), \"New文\");\n        let mut rope = Rope::from(\"中文\");\n        rope.replace(0..2, \"New\");\n        assert_eq!(rope.to_string(), \"New文\");\n        let mut rope = Rope::from(\"中文\");\n        rope.replace(0..3, \"New\");\n        assert_eq!(rope.to_string(), \"New文\");\n        // autocorrect-enable\n        let mut rope = Rope::from(\"中文\");\n        rope.replace(1..4, \"New\");\n        assert_eq!(rope.to_string(), \"New\");\n    }\n\n    #[test]\n    fn test_clip_offset() {\n        let rope = Rope::from(\"Hello 中文🎉 test\\nRope\");\n        // Inside multi-byte character '中' (3 bytes)\n        assert_eq!(rope.clip_offset(5, Bias::Left), 5);\n        assert_eq!(rope.clip_offset(7, Bias::Left), 6);\n        assert_eq!(rope.clip_offset(7, Bias::Right), 9);\n        assert_eq!(rope.clip_offset(9, Bias::Left), 9);\n\n        // Inside multi-byte character '🎉' (4 bytes)\n        assert_eq!(rope.clip_offset(13, Bias::Left), 12);\n        assert_eq!(rope.clip_offset(13, Bias::Right), 16);\n        assert_eq!(rope.clip_offset(16, Bias::Left), 16);\n\n        // At character boundary\n        assert_eq!(rope.clip_offset(5, Bias::Left), 5);\n        assert_eq!(rope.clip_offset(5, Bias::Right), 5);\n\n        // Out of bounds\n        assert_eq!(rope.clip_offset(26, Bias::Left), 26);\n        assert_eq!(rope.clip_offset(100, Bias::Left), 26);\n    }\n\n    #[test]\n    fn test_char_index_to_offset() {\n        let rope = Rope::from(\"a 中文🎉 test\\nRope\");\n        assert_eq!(rope.char_index_to_offset(0), 0);\n        assert_eq!(rope.char_index_to_offset(1), 1);\n        assert_eq!(rope.char_index_to_offset(3), \"a 中\".len());\n        assert_eq!(rope.char_index_to_offset(5), \"a 中文🎉\".len());\n        assert_eq!(rope.char_index_to_offset(6), \"a 中文🎉 \".len());\n\n        assert_eq!(rope.offset_to_char_index(0), 0);\n        assert_eq!(rope.offset_to_char_index(1), 1);\n        assert_eq!(rope.offset_to_char_index(3), 3);\n        assert_eq!(rope.offset_to_char_index(4), 3);\n        assert_eq!(rope.offset_to_char_index(5), 3);\n        assert_eq!(rope.offset_to_char_index(6), 4);\n        assert_eq!(rope.offset_to_char_index(10), 5);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/search.rs",
    "content": "use aho_corasick::AhoCorasick;\nuse rust_i18n::t;\nuse std::{ops::Range, rc::Rc};\n\nuse gpui::{\n    App, AppContext as _, Context, Empty, Entity, FocusHandle, Focusable, Half,\n    InteractiveElement as _, IntoElement, KeyBinding, ParentElement as _, Pixels, Render, Styled,\n    Subscription, Window, actions, div, prelude::FluentBuilder as _,\n};\nuse ropey::Rope;\n\nuse crate::{\n    ActiveTheme, Disableable, ElementExt, IconName, Selectable, Sizable,\n    actions::SelectUp,\n    button::{Button, ButtonVariants},\n    h_flex,\n    input::{\n        Enter, Escape, IndentInline, Input, InputEvent, InputState, RopeExt as _, Search,\n        movement::MoveDirection,\n    },\n    label::Label,\n    v_flex,\n};\n\nconst CONTEXT: &'static str = \"SearchPanel\";\n\nactions!(input, [Tab]);\n\npub(super) fn init(cx: &mut App) {\n    cx.bind_keys(vec![KeyBinding::new(\n        \"shift-enter\",\n        SelectUp,\n        Some(CONTEXT),\n    )]);\n}\n\n#[derive(Debug, Clone)]\npub struct SearchMatcher {\n    text: Rope,\n    pub query: Option<AhoCorasick>,\n\n    pub(super) matched_ranges: Rc<Vec<Range<usize>>>,\n    pub(super) current_match_ix: usize,\n    /// Is in replacing mode, if true, the next update will not reset the current match index.\n    replacing: bool,\n}\n\nimpl SearchMatcher {\n    pub fn new() -> Self {\n        Self {\n            text: \"\".into(),\n            query: None,\n            matched_ranges: Rc::new(Vec::new()),\n            current_match_ix: 0,\n            replacing: false,\n        }\n    }\n\n    /// Update source text and re-match\n    pub(crate) fn update(&mut self, text: &Rope) {\n        if self.text.eq(text) {\n            return;\n        }\n\n        self.text = text.clone();\n        self.update_matches();\n    }\n\n    fn update_matches(&mut self) {\n        let mut new_ranges = Vec::new();\n        if let Some(query) = &self.query {\n            let text = self.text.to_string();\n            // FIXME: Use stream find\n            let matches = query.stream_find_iter(text.as_bytes());\n\n            for query_match in matches.into_iter() {\n                let query_match = query_match.expect(\"query match for select all action\");\n                new_ranges.push(query_match.range());\n            }\n        }\n        self.matched_ranges = Rc::new(new_ranges);\n        if !self.replacing {\n            self.current_match_ix = 0;\n            self.replacing = false;\n        }\n    }\n\n    /// Update the search query and reset the current match index.\n    pub fn update_query(&mut self, query: &str, case_insensitive: bool) {\n        if query.len() > 0 {\n            self.query = Some(\n                AhoCorasick::builder()\n                    .ascii_case_insensitive(case_insensitive)\n                    .build(&[query.to_string()])\n                    .expect(\"failed to build AhoCorasick query in SearchMatcher\"),\n            );\n        } else {\n            self.query = None;\n        }\n        self.update_matches();\n    }\n\n    /// Returns the number of matches found.\n    #[allow(unused)]\n    #[inline]\n    fn len(&self) -> usize {\n        self.matched_ranges.len()\n    }\n\n    fn peek(&self) -> Option<Range<usize>> {\n        self.matched_ranges.get(self.current_match_ix + 1).cloned()\n    }\n\n    fn label(&self) -> String {\n        if self.len() == 0 {\n            return \"0/0\".to_string();\n        }\n        format!(\"{}/{}\", self.current_match_ix + 1, self.len())\n    }\n\n    /// Update the current match index based on the given offset.\n    fn update_cursor_by_offset(&mut self, offset: usize) {\n        for (ix, range) in self.matched_ranges.iter().enumerate() {\n            self.current_match_ix = ix;\n            if range.contains(&offset) || range.end >= offset {\n                return;\n            }\n        }\n    }\n}\n\nimpl Iterator for SearchMatcher {\n    type Item = Range<usize>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        if self.matched_ranges.is_empty() {\n            return None;\n        }\n\n        if self.current_match_ix < self.matched_ranges.len().saturating_sub(1) {\n            self.current_match_ix += 1;\n        } else {\n            self.current_match_ix = 0;\n        }\n\n        self.matched_ranges.get(self.current_match_ix).cloned()\n    }\n}\n\nimpl DoubleEndedIterator for SearchMatcher {\n    fn next_back(&mut self) -> Option<Self::Item> {\n        if self.matched_ranges.is_empty() {\n            return None;\n        }\n\n        if self.current_match_ix == 0 {\n            self.current_match_ix = self.matched_ranges.len();\n        }\n\n        self.current_match_ix -= 1;\n        let item = self.matched_ranges[self.current_match_ix].clone();\n\n        Some(item)\n    }\n}\n\npub(super) struct SearchPanel {\n    editor: Entity<InputState>,\n    search_input: Entity<InputState>,\n    replace_input: Entity<InputState>,\n    case_insensitive: bool,\n    replace_mode: bool,\n    matcher: SearchMatcher,\n    input_width: Pixels,\n\n    open: bool,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl InputState {\n    /// Update the search matcher when text changes.\n    pub(super) fn update_search(&mut self, cx: &mut App) {\n        let Some(search_panel) = self.search_panel.as_ref() else {\n            return;\n        };\n\n        let text = self.text.clone();\n        search_panel.update(cx, |this, _| {\n            this.matcher.update(&text);\n        });\n    }\n\n    pub(super) fn on_action_search(\n        &mut self,\n        _: &Search,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if !self.searchable {\n            return;\n        }\n\n        let search_panel = match self.search_panel.as_ref() {\n            Some(panel) => panel.clone(),\n            None => SearchPanel::new(cx.entity(), window, cx),\n        };\n\n        let text = self.text.clone();\n        let editor = cx.entity();\n        let selected_text = Rope::from(self.selected_text());\n        search_panel.update(cx, |this, cx| {\n            this.editor = editor;\n            this.matcher.update(&text);\n            this.show(&selected_text, window, cx);\n        });\n        self.search_panel = Some(search_panel);\n        cx.notify();\n    }\n}\n\nimpl SearchPanel {\n    pub fn new(editor: Entity<InputState>, window: &mut Window, cx: &mut App) -> Entity<Self> {\n        let search_input = cx.new(|cx| InputState::new(window, cx));\n        let replace_input = cx.new(|cx| InputState::new(window, cx));\n\n        cx.new(|cx| {\n            let _subscriptions =\n                vec![\n                    cx.subscribe(&search_input, |this: &mut Self, _, ev: &InputEvent, cx| {\n                        // Handle search input changes\n                        match ev {\n                            InputEvent::Change => {\n                                this.update_search_query(cx);\n                            }\n                            _ => {}\n                        }\n                    }),\n                ];\n\n            Self {\n                editor,\n                search_input,\n                replace_input,\n                case_insensitive: true,\n                replace_mode: false,\n                matcher: SearchMatcher::new(),\n                open: true,\n                input_width: Pixels::ZERO,\n                _subscriptions,\n            }\n        })\n    }\n\n    pub(super) fn show(\n        &mut self,\n        selected_text: &Rope,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.open = true;\n        self.search_input\n            .read(cx)\n            .focus_handle\n            .clone()\n            .focus(window, cx);\n\n        self.search_input.update(cx, |this, cx| {\n            if selected_text.len() > 0 {\n                // Set value will emit to update_search_query\n                this.set_value(selected_text.to_string(), window, cx);\n            }\n            this.select_all(&super::SelectAll, window, cx);\n        });\n    }\n\n    fn update_search_query(&mut self, cx: &mut Context<Self>) {\n        let query = self.search_input.read(cx).value();\n        let visible_range_offset = self\n            .editor\n            .read(cx)\n            .last_layout\n            .as_ref()\n            .map(|l| l.visible_range_offset.clone());\n\n        self.matcher\n            .update_query(query.as_str(), self.case_insensitive);\n\n        if let Some(visible_range_offset) = visible_range_offset {\n            self.matcher\n                .update_cursor_by_offset(visible_range_offset.start);\n        }\n        cx.notify();\n    }\n\n    pub(super) fn hide(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        self.open = false;\n        self.editor.read(cx).focus_handle.clone().focus(window, cx);\n        cx.notify();\n    }\n\n    fn on_action_prev(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {\n        self.prev(window, cx);\n    }\n\n    fn on_action_next(&mut self, _: &Enter, window: &mut Window, cx: &mut Context<Self>) {\n        self.next(window, cx);\n    }\n\n    fn on_action_escape(&mut self, _: &Escape, window: &mut Window, cx: &mut Context<Self>) {\n        self.hide(window, cx);\n    }\n\n    fn on_action_tab(&mut self, _: &IndentInline, window: &mut Window, cx: &mut Context<Self>) {\n        self.editor.focus_handle(cx).focus(window, cx);\n    }\n\n    fn prev(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        if let Some(range) = self.matcher.next_back() {\n            self.editor.update(cx, |state, cx| {\n                state.scroll_to(range.start, Some(MoveDirection::Up), cx);\n            });\n        }\n    }\n\n    fn next(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        if let Some(range) = self.matcher.next() {\n            self.editor.update(cx, |state, cx| {\n                state.scroll_to(range.end, Some(MoveDirection::Down), cx);\n            });\n        }\n    }\n\n    pub(super) fn matcher(&self) -> Option<&SearchMatcher> {\n        if !self.open {\n            return None;\n        }\n\n        Some(&self.matcher)\n    }\n\n    fn replace_next(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        let new_text = self.replace_input.read(cx).value();\n        self.matcher.replacing = true;\n        if let Some(range) = self\n            .matcher\n            .matched_ranges\n            .get(self.matcher.current_match_ix)\n            .cloned()\n        {\n            let text_state = self.editor.clone();\n\n            let next_range = self.matcher.peek().unwrap_or(range.clone());\n            cx.spawn_in(window, async move |_, cx| {\n                cx.update(|window, cx| {\n                    text_state.update(cx, |state, cx| {\n                        let range_utf16 = state.range_to_utf16(&range);\n                        state.scroll_to(next_range.end, Some(MoveDirection::Down), cx);\n                        state.replace_text_in_range_silent(\n                            Some(range_utf16),\n                            new_text.as_str(),\n                            window,\n                            cx,\n                        );\n                    });\n                })\n            })\n            .detach();\n        }\n    }\n\n    fn replace_all(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        let new_text = self.replace_input.read(cx).value();\n        self.matcher.replacing = true;\n        let ranges = self.matcher.matched_ranges.clone();\n        if ranges.is_empty() {\n            return;\n        }\n\n        let editor = self.editor.clone();\n        cx.spawn_in(window, async move |_, cx| {\n            cx.update(|window, cx| {\n                editor.update(cx, |state, cx| {\n                    // Replace from the end to avoid messing up the ranges.\n                    let mut rope = state.text.clone();\n                    for range in ranges.iter().rev() {\n                        rope.replace(range.clone(), new_text.as_str());\n                    }\n                    state.replace_text_in_range_silent(\n                        Some(0..state.text.len()),\n                        &rope.to_string(),\n                        window,\n                        cx,\n                    );\n                    state.scroll_to(0, Some(MoveDirection::Down), cx);\n                });\n            })\n        })\n        .detach();\n    }\n}\n\nimpl Focusable for SearchPanel {\n    fn focus_handle(&self, cx: &App) -> FocusHandle {\n        self.search_input.read(cx).focus_handle.clone()\n    }\n}\n\nimpl Render for SearchPanel {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        if !self.open {\n            return Empty.into_any_element();\n        }\n\n        let has_matches = self.matcher.len() > 0;\n\n        v_flex()\n            .id(\"search-panel\")\n            .occlude()\n            .track_focus(&self.focus_handle(cx))\n            .key_context(CONTEXT)\n            .on_action(cx.listener(Self::on_action_prev))\n            .on_action(cx.listener(Self::on_action_next))\n            .on_action(cx.listener(Self::on_action_escape))\n            .on_action(cx.listener(Self::on_action_tab))\n            .font_family(cx.theme().font_family.clone())\n            .items_center()\n            .py_2()\n            .px_3()\n            .w_full()\n            .gap_1()\n            .bg(cx.theme().popover)\n            .border_b_1()\n            .rounded(cx.theme().radius.half())\n            .border_color(cx.theme().border)\n            .child(\n                h_flex()\n                    .w_full()\n                    .gap_2()\n                    .child(\n                        div()\n                            .flex_1()\n                            .gap_1()\n                            .child(\n                                Input::new(&self.search_input)\n                                    .focus_bordered(false)\n                                    .suffix(\n                                        Button::new(\"case-insensitive\")\n                                            .selected(!self.case_insensitive)\n                                            .xsmall()\n                                            .compact()\n                                            .ghost()\n                                            .icon(IconName::CaseSensitive)\n                                            .on_click(cx.listener(|this, _, _, cx| {\n                                                this.case_insensitive = !this.case_insensitive;\n                                                this.update_search_query(cx);\n                                                cx.notify();\n                                            })),\n                                    )\n                                    .small()\n                                    .w_full()\n                                    .shadow_none(),\n                            )\n                            .on_prepaint({\n                                let view = cx.entity();\n                                move |bounds, _, cx| {\n                                    view.update(cx, |r, _| r.input_width = bounds.size.width)\n                                }\n                            }),\n                    )\n                    .child(\n                        Button::new(\"replace-mode\")\n                            .xsmall()\n                            .ghost()\n                            .icon(IconName::Replace)\n                            .selected(self.replace_mode)\n                            .on_click(cx.listener(|this, _, window, cx| {\n                                this.replace_mode = !this.replace_mode;\n                                if this.replace_mode {\n                                    this.replace_input\n                                        .read(cx)\n                                        .focus_handle\n                                        .clone()\n                                        .focus(window, cx);\n                                } else {\n                                    this.search_input\n                                        .read(cx)\n                                        .focus_handle\n                                        .clone()\n                                        .focus(window, cx);\n                                }\n                                cx.notify();\n                            })),\n                    )\n                    .child(\n                        Button::new(\"prev\")\n                            .xsmall()\n                            .ghost()\n                            .icon(IconName::ChevronLeft)\n                            .disabled(!has_matches)\n                            .on_click(cx.listener(|this, _, window, cx| {\n                                this.prev(window, cx);\n                            })),\n                    )\n                    .child(\n                        Button::new(\"next\")\n                            .xsmall()\n                            .ghost()\n                            .icon(IconName::ChevronRight)\n                            .disabled(!has_matches)\n                            .on_click(cx.listener(|this, _, window, cx| {\n                                this.next(window, cx);\n                            })),\n                    )\n                    .child(\n                        Label::new(self.matcher.label())\n                            .when(!has_matches, |this| {\n                                this.text_color(cx.theme().muted_foreground)\n                            })\n                            .text_left()\n                            .min_w_16(),\n                    )\n                    .child(div().w_7())\n                    .child(\n                        Button::new(\"close\")\n                            .xsmall()\n                            .ghost()\n                            .icon(IconName::Close)\n                            .on_click(cx.listener(|this, _, window, cx| {\n                                this.on_action_escape(&Escape, window, cx);\n                            })),\n                    ),\n            )\n            .when(self.replace_mode, |this| {\n                this.child(\n                    h_flex()\n                        .w_full()\n                        .gap_2()\n                        .child(\n                            Input::new(&self.replace_input)\n                                .focus_bordered(false)\n                                .small()\n                                .w(self.input_width)\n                                .shadow_none(),\n                        )\n                        .child(\n                            Button::new(\"replace-one\")\n                                .small()\n                                .label(t!(\"Input.Replace\"))\n                                .disabled(!has_matches)\n                                .on_click(cx.listener(|this, _, window, cx| {\n                                    this.replace_next(window, cx);\n                                })),\n                        )\n                        .child(\n                            Button::new(\"replace-all\")\n                                .small()\n                                .label(t!(\"Input.Replace All\"))\n                                .disabled(!has_matches)\n                                .on_click(cx.listener(|this, _, window, cx| {\n                                    this.replace_all(window, cx);\n                                })),\n                        ),\n                )\n            })\n            .into_any_element()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_search() {\n        let mut matcher = SearchMatcher::new();\n        matcher.update(&Rope::from(\"Hello 世界 this is a Is test string.\"));\n        matcher.update_query(\"Is\", true);\n\n        assert_eq!(matcher.len(), 3);\n        let mut matches = matcher.clone();\n        assert_eq!(matches.current_match_ix, 0);\n        assert_eq!(matches.next(), Some(18..20));\n        assert_eq!(matches.next(), Some(23..25));\n        assert_eq!(matches.current_match_ix, 2);\n        assert_eq!(matches.next(), Some(15..17));\n        assert_eq!(matches.current_match_ix, 0);\n        assert_eq!(matches.next_back(), Some(23..25));\n        assert_eq!(matches.current_match_ix, 2);\n        assert_eq!(matches.next_back(), Some(18..20));\n        assert_eq!(matches.current_match_ix, 1);\n        assert_eq!(matches.next_back(), Some(15..17));\n        assert_eq!(matches.current_match_ix, 0);\n        assert_eq!(matches.next_back(), Some(23..25));\n\n        matcher.update_query(\"IS\", false);\n        assert_eq!(matcher.len(), 0);\n        assert_eq!(matcher.next(), None);\n        assert_eq!(matcher.next_back(), None);\n    }\n\n    #[test]\n    fn test_search_label() {\n        let mut matcher = SearchMatcher::new();\n        matcher.update(&Rope::from(\"Hello 世界 this is a Is test string.\"));\n        matcher.update_query(\"Is\", true);\n        assert_eq!(matcher.label(), \"1/3\");\n        matcher.next();\n        assert_eq!(matcher.label(), \"2/3\");\n        matcher.next();\n        assert_eq!(matcher.label(), \"3/3\");\n        matcher.next();\n        assert_eq!(matcher.label(), \"1/3\");\n\n        matcher.update_query(\"IS\", false);\n        assert_eq!(matcher.label(), \"0/0\");\n    }\n\n    #[test]\n    fn test_select_range_start() {\n        let mut matcher = SearchMatcher::new();\n        matcher.matched_ranges = Rc::new(vec![5..10, 15..20, 25..30]);\n        matcher.update_cursor_by_offset(0);\n        assert_eq!(matcher.current_match_ix, 0);\n\n        matcher.update_cursor_by_offset(5);\n        assert_eq!(matcher.current_match_ix, 0);\n\n        matcher.update_cursor_by_offset(12);\n        assert_eq!(matcher.current_match_ix, 1);\n\n        matcher.update_cursor_by_offset(16);\n        assert_eq!(matcher.current_match_ix, 1);\n\n        matcher.update_cursor_by_offset(30);\n        assert_eq!(matcher.current_match_ix, 2);\n\n        matcher.update_cursor_by_offset(31);\n        assert_eq!(matcher.current_match_ix, 2);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/selection.rs",
    "content": "use std::{char, ops::Range};\n\nuse gpui::{Context, Window};\nuse ropey::Rope;\nuse sum_tree::Bias;\n\nuse crate::{RopeExt as _, input::InputState};\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum CharType {\n    /// a-z, A-Z, 0-9, _\n    Word,\n    /// '\\t', ' ', '\\u{00A0}' etc.\n    Whitespace,\n    /// \\n, \\r\n    Newline,\n    /// . , ; : ( ) [ ] { } ... or CJK characters: `汉`, `🎉` etc.\n    Other,\n}\n\n/// Implementation based on <https://github.com/zed-industries/zed/blob/main/crates/gpui/src/text_system/line_wrapper.rs>\nfn is_word_char(c: char) -> bool {\n    matches!(c, '_' ) ||\n    // ASCII alphanumeric characters, for English, numbers: `Hello123`, etc.\n    c.is_ascii_alphanumeric() ||\n    // Latin script in Unicode for French, German, Spanish, etc.\n    // Latin-1 Supplement\n    // https://en.wikipedia.org/wiki/Latin-1_Supplement\n    matches!(c, '\\u{00C0}'..='\\u{00FF}') ||\n    // Latin Extended-A\n    // https://en.wikipedia.org/wiki/Latin_Extended-A\n    matches!(c, '\\u{0100}'..='\\u{017F}') ||\n    // Latin Extended-B\n    // https://en.wikipedia.org/wiki/Latin_Extended-B\n    matches!(c, '\\u{0180}'..='\\u{024F}') ||\n    // Cyrillic for Russian, Ukrainian, etc.\n    // https://en.wikipedia.org/wiki/Cyrillic_script_in_Unicode\n    matches!(c, '\\u{0400}'..='\\u{04FF}') ||\n\n    // Vietnamese (https://vietunicode.sourceforge.net/charset/)\n    matches!(c, '\\u{1E00}'..='\\u{1EFF}') || // Latin Extended Additional\n    matches!(c, '\\u{0300}'..='\\u{036F}') // Combining Diacritical Marks\n}\n\nimpl From<char> for CharType {\n    fn from(c: char) -> Self {\n        match c {\n            c if is_word_char(c) => CharType::Word,\n            c if c == '\\n' || c == '\\r' => CharType::Newline,\n            c if c.is_whitespace() => CharType::Whitespace,\n            _ => CharType::Other,\n        }\n    }\n}\n\nimpl CharType {\n    /// Check if two CharTypes are connectable\n    fn is_connectable(self, c: char) -> bool {\n        let other = CharType::from(c);\n        match (self, other) {\n            (CharType::Word, CharType::Word) => true,\n            (CharType::Whitespace, CharType::Whitespace) => true,\n            _ => false,\n        }\n    }\n}\n\nimpl InputState {\n    /// Select the word at the given offset on double-click.\n    ///\n    /// The offset is the UTF-8 offset.\n    pub(super) fn select_word(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {\n        let Some(range) = TextSelector::word_range(&self.text, offset) else {\n            return;\n        };\n\n        self.selected_range = (range.start..range.end).into();\n        self.selected_word_range = Some(self.selected_range);\n        cx.notify()\n    }\n\n    /// Select the line at the given offset on triple-click.\n    ///\n    /// The offset is the UTF-8 offset.\n    pub(super) fn select_line(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {\n        let range = TextSelector::line_range(&self.text, offset);\n        self.selected_range = (range.start..range.end).into();\n        self.selected_word_range = None;\n        cx.notify()\n    }\n}\n\nstruct TextSelector;\nimpl TextSelector {\n    /// Select a line in the given text at the specified offset.\n    ///\n    /// The offset is the UTF-8 offset.\n    ///\n    /// Returns the start and end offsets of the selected line.\n    pub fn line_range(text: &Rope, offset: usize) -> Range<usize> {\n        let offset = text.clip_offset(offset, Bias::Left);\n        let row = text.offset_to_point(offset).row;\n        let start = text.line_start_offset(row);\n        let end = text.line_end_offset(row);\n\n        start..end\n    }\n\n    /// Select a word in the given text at the specified offset.\n    ///\n    /// The offset is the UTF-8 offset.\n    ///\n    /// Returns the start and end offsets of the selected word.\n    pub fn word_range(text: &Rope, offset: usize) -> Option<Range<usize>> {\n        let offset = text.clip_offset(offset, Bias::Left);\n        let Some(char) = text.char_at(offset) else {\n            return None;\n        };\n\n        let char_type = CharType::from(char);\n        let mut start = offset;\n        let mut end = offset + char.len_utf8();\n        let prev_chars = text.chars_at(start).reversed().take(128);\n        let next_chars = text.chars_at(end).take(128);\n\n        for ch in prev_chars {\n            if char_type.is_connectable(ch) {\n                start -= ch.len_utf8();\n            } else {\n                break;\n            }\n        }\n\n        for ch in next_chars {\n            if char_type.is_connectable(ch) {\n                end += ch.len_utf8();\n            } else {\n                break;\n            }\n        }\n\n        Some(start..end)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use ropey::Rope;\n\n    #[test]\n    fn test_char_type_from_char() {\n        assert_eq!(CharType::from('a'), CharType::Word);\n        assert_eq!(CharType::from('Z'), CharType::Word);\n        assert_eq!(CharType::from('0'), CharType::Word);\n        assert_eq!(CharType::from('_'), CharType::Word);\n        assert_eq!(CharType::from('.'), CharType::Other);\n        assert_eq!(CharType::from(','), CharType::Other);\n        assert_eq!(CharType::from(';'), CharType::Other);\n        assert_eq!(CharType::from('!'), CharType::Other);\n        assert_eq!(CharType::from('?'), CharType::Other);\n        assert_eq!(CharType::from('['), CharType::Other);\n        assert_eq!(CharType::from('{'), CharType::Other);\n        assert_eq!(CharType::from(' '), CharType::Whitespace);\n        assert_eq!(CharType::from('\\t'), CharType::Whitespace);\n        assert_eq!(CharType::from('\\u{00A0}'), CharType::Whitespace);\n        assert_eq!(CharType::from('\\n'), CharType::Newline);\n        assert_eq!(CharType::from('\\r'), CharType::Newline);\n        assert_eq!(CharType::from('汉'), CharType::Other);\n        // European letters\n        assert_eq!(CharType::from('é'), CharType::Word);\n        assert_eq!(CharType::from('ä'), CharType::Word);\n        assert_eq!(CharType::from('ö'), CharType::Word);\n        assert_eq!(CharType::from('ü'), CharType::Word);\n        //Cyrillic letters\n        assert_eq!(CharType::from('д'), CharType::Word);\n    }\n\n    #[test]\n    fn test_word_range() {\n        use indoc::indoc;\n\n        let rope = Rope::from(indoc! {\n            r#\"\n            test text:\n            abcde 中文🎉 test\n            hello[()]\n            test_connector ____\n            Rope\n            rök\n            grande île\n            \"#\n        });\n\n        let tests = vec![\n            (0, 0, Some(\"test\")),\n            (0, 4, Some(\" \")),\n            (1, 0, Some(\"abcde\")),\n            (1, 4, Some(\"abcde\")),\n            (1, 5, Some(\" \")),\n            (1, 6, Some(\"中\")),\n            (1, 9, Some(\"文\")),\n            (1, 13, Some(\"🎉\")),\n            (1, 20, Some(\"test\")),\n            (2, 5, Some(\"[\")),\n            (2, 6, Some(\"(\")),\n            (2, 7, Some(\")\")),\n            (2, 8, Some(\"]\")),\n            (3, 5, Some(\"test_connector\")),\n            (3, 14, Some(\" \")),\n            (3, 16, Some(\"____\")),\n            (4, 0, Some(\"Rope\")),\n            (5, 0, Some(\"rök\")),\n            (6, 8, Some(\"île\")),\n        ];\n\n        for (line, column, expected) in tests {\n            let line_start_offset = rope.line_start_offset(line);\n            let offset = line_start_offset + column;\n            let range = TextSelector::word_range(&rope, offset);\n\n            let actual = range.map(|r| rope.slice(r).to_string());\n            let expect = expected.map(|s| s.to_string());\n            assert_eq!(actual, expect, \"line {}, column {}\", line, column);\n        }\n    }\n\n    #[test]\n    fn test_line_range() {\n        let rope = Rope::from(\"first line\\nsecond line\\nthird\");\n        let tests = vec![\n            (0, 0, \"first line\"),\n            (0, 5, \"first line\"),\n            (1, 3, \"second line\"),\n            (2, 1, \"third\"),\n        ];\n\n        for (line, column, expected) in tests {\n            let line_start_offset = rope.line_start_offset(line);\n            let offset = line_start_offset + column;\n            let range = TextSelector::line_range(&rope, offset);\n\n            let actual = rope.slice(range).to_string();\n            assert_eq!(actual, expected, \"line {}, column {}\", line, column);\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/input/state.rs",
    "content": "//! A text input field that allows the user to enter text.\n//!\n//! Based on the `Input` example from the `gpui` crate.\n//! https://github.com/zed-industries/zed/blob/main/crates/gpui/examples/input.rs\nuse anyhow::Result;\nuse gpui::{\n    Action, App, AppContext, Bounds, ClipboardItem, Context, Entity, EntityInputHandler,\n    EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding,\n    KeyDownEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _,\n    Pixels, Point, Render, ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Styled as _,\n    Subscription, Task, UTF16Selection, Window, actions, div, point, prelude::FluentBuilder as _,\n    px,\n};\nuse gpui::{Half, TextAlign};\nuse ropey::{Rope, RopeSlice};\nuse serde::Deserialize;\nuse std::ops::Range;\nuse std::rc::Rc;\nuse sum_tree::Bias;\nuse unicode_segmentation::*;\n\nuse super::{\n    DisplayMap, blink_cursor::BlinkCursor, change::Change, element::TextElement,\n    mask_pattern::MaskPattern, mode::InputMode, number_input,\n};\nuse crate::Size;\nuse crate::actions::{SelectDown, SelectLeft, SelectRight, SelectUp};\nuse crate::highlighter::DiagnosticSet;\n#[cfg(not(target_family = \"wasm\"))]\nuse crate::highlighter::LanguageRegistry;\nuse crate::input::blink_cursor::CURSOR_WIDTH;\nuse crate::input::movement::MoveDirection;\nuse crate::input::{\n    HoverDefinition, InlineCompletion, Lsp, Position, RopeExt as _, Selection,\n    display_map::LineLayout,\n    element::RIGHT_MARGIN,\n    popovers::{ContextMenu, DiagnosticPopover, HoverPopover, MouseContextMenu},\n    search::{self, SearchPanel},\n};\nuse crate::{Root, history::History};\n\n#[derive(Action, Clone, PartialEq, Eq, Deserialize)]\n#[action(namespace = input, no_json)]\npub struct Enter {\n    /// Is confirm with secondary.\n    pub secondary: bool,\n}\n\nactions!(\n    input,\n    [\n        Backspace,\n        Delete,\n        DeleteToBeginningOfLine,\n        DeleteToEndOfLine,\n        DeleteToPreviousWordStart,\n        DeleteToNextWordEnd,\n        Indent,\n        Outdent,\n        IndentInline,\n        OutdentInline,\n        MoveUp,\n        MoveDown,\n        MoveLeft,\n        MoveRight,\n        MoveHome,\n        MoveEnd,\n        MovePageUp,\n        MovePageDown,\n        SelectAll,\n        SelectToStartOfLine,\n        SelectToEndOfLine,\n        SelectToStart,\n        SelectToEnd,\n        SelectToPreviousWordStart,\n        SelectToNextWordEnd,\n        ShowCharacterPalette,\n        Copy,\n        Cut,\n        Paste,\n        Undo,\n        Redo,\n        MoveToStartOfLine,\n        MoveToEndOfLine,\n        MoveToStart,\n        MoveToEnd,\n        MoveToPreviousWord,\n        MoveToNextWord,\n        Escape,\n        ToggleCodeActions,\n        Search,\n        GoToDefinition,\n    ]\n);\n\n#[derive(Clone)]\npub enum InputEvent {\n    Change,\n    PressEnter { secondary: bool },\n    Focus,\n    Blur,\n}\n\npub(super) const CONTEXT: &str = \"Input\";\n\npub(crate) fn init(cx: &mut App) {\n    cx.bind_keys([\n        KeyBinding::new(\"backspace\", Backspace, Some(CONTEXT)),\n        KeyBinding::new(\"shift-backspace\", Backspace, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"ctrl-backspace\", Backspace, Some(CONTEXT)),\n        KeyBinding::new(\"delete\", Delete, Some(CONTEXT)),\n        KeyBinding::new(\"shift-delete\", Delete, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-backspace\", DeleteToBeginningOfLine, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-delete\", DeleteToEndOfLine, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"alt-backspace\", DeleteToPreviousWordStart, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-backspace\", DeleteToPreviousWordStart, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"alt-delete\", DeleteToNextWordEnd, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-delete\", DeleteToNextWordEnd, Some(CONTEXT)),\n        KeyBinding::new(\"enter\", Enter { secondary: false }, Some(CONTEXT)),\n        KeyBinding::new(\"shift-enter\", Enter { secondary: false }, Some(CONTEXT)),\n        KeyBinding::new(\"secondary-enter\", Enter { secondary: true }, Some(CONTEXT)),\n        KeyBinding::new(\"escape\", Escape, Some(CONTEXT)),\n        KeyBinding::new(\"up\", MoveUp, Some(CONTEXT)),\n        KeyBinding::new(\"down\", MoveDown, Some(CONTEXT)),\n        KeyBinding::new(\"left\", MoveLeft, Some(CONTEXT)),\n        KeyBinding::new(\"right\", MoveRight, Some(CONTEXT)),\n        KeyBinding::new(\"pageup\", MovePageUp, Some(CONTEXT)),\n        KeyBinding::new(\"pagedown\", MovePageDown, Some(CONTEXT)),\n        KeyBinding::new(\"tab\", IndentInline, Some(CONTEXT)),\n        KeyBinding::new(\"shift-tab\", OutdentInline, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-]\", Indent, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-]\", Indent, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-[\", Outdent, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-[\", Outdent, Some(CONTEXT)),\n        KeyBinding::new(\"shift-left\", SelectLeft, Some(CONTEXT)),\n        KeyBinding::new(\"shift-right\", SelectRight, Some(CONTEXT)),\n        KeyBinding::new(\"shift-up\", SelectUp, Some(CONTEXT)),\n        KeyBinding::new(\"shift-down\", SelectDown, Some(CONTEXT)),\n        KeyBinding::new(\"home\", MoveHome, Some(CONTEXT)),\n        KeyBinding::new(\"end\", MoveEnd, Some(CONTEXT)),\n        KeyBinding::new(\"shift-home\", SelectToStartOfLine, Some(CONTEXT)),\n        KeyBinding::new(\"shift-end\", SelectToEndOfLine, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"ctrl-shift-a\", SelectToStartOfLine, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"ctrl-shift-e\", SelectToEndOfLine, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"shift-cmd-left\", SelectToStartOfLine, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"shift-cmd-right\", SelectToEndOfLine, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"alt-shift-left\", SelectToPreviousWordStart, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-shift-left\", SelectToPreviousWordStart, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"alt-shift-right\", SelectToNextWordEnd, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-shift-right\", SelectToNextWordEnd, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"ctrl-cmd-space\", ShowCharacterPalette, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-a\", SelectAll, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-a\", SelectAll, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-c\", Copy, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-c\", Copy, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-x\", Cut, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-x\", Cut, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-v\", Paste, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-v\", Paste, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"ctrl-a\", MoveHome, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-left\", MoveHome, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"ctrl-e\", MoveEnd, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-right\", MoveEnd, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-z\", Undo, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-shift-z\", Redo, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-up\", MoveToStart, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-down\", MoveToEnd, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"alt-left\", MoveToPreviousWord, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"alt-right\", MoveToNextWord, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-left\", MoveToPreviousWord, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-right\", MoveToNextWord, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-shift-up\", SelectToStart, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-shift-down\", SelectToEnd, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-z\", Undo, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-y\", Redo, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-.\", ToggleCodeActions, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-.\", ToggleCodeActions, Some(CONTEXT)),\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-f\", Search, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-f\", Search, Some(CONTEXT)),\n    ]);\n\n    search::init(cx);\n    number_input::init(cx);\n}\n\n/// Whitespace indicators for rendering spaces and tabs.\n#[derive(Clone, Default)]\npub(crate) struct WhitespaceIndicators {\n    /// Shaped line for space character indicator (•)\n    pub(crate) space: ShapedLine,\n    /// Shaped line for tab character indicator (→)\n    pub(crate) tab: ShapedLine,\n}\n\n#[derive(Clone)]\npub(super) struct LastLayout {\n    /// The visible range (no wrap) of lines in the viewport, the value is row (0-based) index.\n    /// This is the buffer line range that encompasses all visible lines.\n    pub(super) visible_range: Range<usize>,\n    /// The list of visible buffer line indices (excludes hidden/folded lines).\n    /// Parallel to `lines`: `visible_buffer_lines[i]` is the buffer line index of `lines[i]`.\n    pub(super) visible_buffer_lines: Vec<usize>,\n    /// Byte offset of each visible buffer line in the Rope (parallel to visible_buffer_lines/lines).\n    pub(super) visible_line_byte_offsets: Vec<usize>,\n    /// The first visible line top position in scroll viewport.\n    pub(super) visible_top: Pixels,\n    /// The range of byte offset of the visible lines.\n    pub(super) visible_range_offset: Range<usize>,\n    /// The last layout lines (Only have visible lines, no empty entries for hidden lines).\n    pub(super) lines: Rc<Vec<LineLayout>>,\n    /// The line_height of text layout, this will change will InputElement painted.\n    pub(super) line_height: Pixels,\n    /// The wrap width of text layout, this will change will InputElement painted.\n    pub(super) wrap_width: Option<Pixels>,\n    /// The line number area width of text layout, if not line number, this will be 0px.\n    pub(super) line_number_width: Pixels,\n    /// The cursor position (top, left) in pixels.\n    pub(super) cursor_bounds: Option<Bounds<Pixels>>,\n    /// The text align of the text layout.\n    pub(super) text_align: TextAlign,\n    /// The content width of the text layout.\n    pub(super) content_width: Pixels,\n}\n\nimpl LastLayout {\n    /// Get the line layout for the given buffer row (0-based).\n    ///\n    /// Uses binary search on `visible_buffer_lines` to find the line.\n    /// Returns None if the row is not visible (out of range or folded).\n    pub(crate) fn line(&self, row: usize) -> Option<&LineLayout> {\n        let pos = self.visible_buffer_lines.binary_search(&row).ok()?;\n        self.lines.get(pos)\n    }\n\n    /// Get the alignment offset for the given line width.\n    pub(super) fn alignment_offset(&self, line_width: Pixels) -> Pixels {\n        match self.text_align {\n            TextAlign::Left => px(0.),\n            TextAlign::Center => (self.content_width - line_width).half().max(px(0.)),\n            TextAlign::Right => (self.content_width - line_width).max(px(0.)),\n        }\n    }\n}\n\n/// InputState to keep editing state of the [`super::Input`].\npub struct InputState {\n    pub(super) focus_handle: FocusHandle,\n    pub(super) mode: InputMode,\n    pub(super) text: Rope,\n    pub(super) display_map: DisplayMap,\n    pub(super) history: History<Change>,\n    pub(super) blink_cursor: Entity<BlinkCursor>,\n    pub(super) loading: bool,\n    /// Range in UTF-8 length for the selected text.\n    ///\n    /// - \"Hello 世界💝\" = 16\n    /// - \"💝\" = 4\n    pub(super) selected_range: Selection,\n    pub(super) search_panel: Option<Entity<SearchPanel>>,\n    pub(super) searchable: bool,\n    /// Range for save the selected word, use to keep word range when drag move.\n    pub(super) selected_word_range: Option<Selection>,\n    pub(super) selection_reversed: bool,\n    /// The marked range is the temporary insert text on IME typing.\n    pub(super) ime_marked_range: Option<Selection>,\n    pub(super) last_layout: Option<LastLayout>,\n    pub(super) last_cursor: Option<usize>,\n    /// The input container bounds\n    pub(super) input_bounds: Bounds<Pixels>,\n    /// The text bounds\n    pub(super) last_bounds: Option<Bounds<Pixels>>,\n    pub(super) last_selected_range: Option<Selection>,\n    pub(super) selecting: bool,\n    pub(super) size: Size,\n    pub(super) disabled: bool,\n    pub(super) masked: bool,\n    pub(super) clean_on_escape: bool,\n    pub(super) soft_wrap: bool,\n    pub(super) show_whitespaces: bool,\n    pub(super) pattern: Option<regex::Regex>,\n    pub(super) validate: Option<Box<dyn Fn(&str, &mut Context<Self>) -> bool + 'static>>,\n    pub(crate) scroll_handle: ScrollHandle,\n    /// The deferred scroll offset to apply on next layout.\n    pub(crate) deferred_scroll_offset: Option<Point<Pixels>>,\n    /// The size of the scrollable content.\n    pub(crate) scroll_size: gpui::Size<Pixels>,\n    pub(super) text_align: TextAlign,\n\n    /// The mask pattern for formatting the input text\n    pub(crate) mask_pattern: MaskPattern,\n    pub(super) placeholder: SharedString,\n\n    /// Popover\n    diagnostic_popover: Option<Entity<DiagnosticPopover>>,\n    /// Completion/CodeAction context menu\n    pub(super) context_menu: Option<ContextMenu>,\n    pub(super) mouse_context_menu: Entity<MouseContextMenu>,\n    /// A flag to indicate if we are currently inserting a completion item.\n    pub(super) completion_inserting: bool,\n    pub(super) hover_popover: Option<Entity<HoverPopover>>,\n    /// The LSP definitions locations for \"Go to Definition\" feature.\n    pub(super) hover_definition: HoverDefinition,\n\n    pub lsp: Lsp,\n\n    /// A flag to indicate if we have a pending update to the text.\n    ///\n    /// If true, will call some update (for example LSP, Syntax Highlight) before render.\n    _pending_update: bool,\n    /// A flag to indicate if we should ignore the next completion event.\n    pub(super) silent_replace_text: bool,\n\n    /// To remember the horizontal column (x-coordinate) of the cursor position for keep column for move up/down.\n    ///\n    /// The first element is the x-coordinate (Pixels), preferred to use this.\n    /// The second element is the column (usize), fallback to use this.\n    pub(super) preferred_column: Option<(Pixels, usize)>,\n    _subscriptions: Vec<Subscription>,\n\n    pub(super) _context_menu_task: Task<Result<()>>,\n    pub(super) inline_completion: InlineCompletion,\n}\n\nimpl EventEmitter<InputEvent> for InputState {}\n\nimpl InputState {\n    /// Create a Input state with default [`InputMode::SingleLine`] mode.\n    ///\n    /// See also: [`Self::multi_line`], [`Self::auto_grow`] to set other mode.\n    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let focus_handle = cx.focus_handle().tab_stop(true);\n        let blink_cursor = cx.new(|_| BlinkCursor::new());\n        let history = History::new().group_interval(std::time::Duration::from_secs(1));\n\n        let _subscriptions = vec![\n            // Observe the blink cursor to repaint the view when it changes.\n            cx.observe(&blink_cursor, |_, _, cx| cx.notify()),\n            // Blink the cursor when the window is active, pause when it's not.\n            cx.observe_window_activation(window, |input, window, cx| {\n                if window.is_window_active() {\n                    let focus_handle = input.focus_handle.clone();\n                    if focus_handle.is_focused(window) {\n                        input.blink_cursor.update(cx, |blink_cursor, cx| {\n                            blink_cursor.start(cx);\n                        });\n                    }\n                }\n            }),\n            cx.on_focus(&focus_handle, window, Self::on_focus),\n            cx.on_blur(&focus_handle, window, Self::on_blur),\n        ];\n\n        let text_style = window.text_style();\n        let mouse_context_menu = MouseContextMenu::new(cx.entity(), window, cx);\n\n        Self {\n            focus_handle: focus_handle.clone(),\n            text: \"\".into(),\n            display_map: DisplayMap::new(text_style.font(), window.rem_size(), None),\n            blink_cursor,\n            history,\n            selected_range: Selection::default(),\n            search_panel: None,\n            searchable: false,\n            selected_word_range: None,\n            selection_reversed: false,\n            ime_marked_range: None,\n            input_bounds: Bounds::default(),\n            selecting: false,\n            disabled: false,\n            masked: false,\n            clean_on_escape: false,\n            soft_wrap: true,\n            show_whitespaces: false,\n            loading: false,\n            pattern: None,\n            validate: None,\n            mode: InputMode::default(),\n            last_layout: None,\n            last_bounds: None,\n            last_selected_range: None,\n            last_cursor: None,\n            scroll_handle: ScrollHandle::new(),\n            scroll_size: gpui::size(px(0.), px(0.)),\n            deferred_scroll_offset: None,\n            preferred_column: None,\n            placeholder: SharedString::default(),\n            mask_pattern: MaskPattern::default(),\n            text_align: TextAlign::Left,\n            lsp: Lsp::default(),\n            diagnostic_popover: None,\n            context_menu: None,\n            mouse_context_menu,\n            completion_inserting: false,\n            hover_popover: None,\n            hover_definition: HoverDefinition::default(),\n            silent_replace_text: false,\n            size: Size::default(),\n            _subscriptions,\n            _context_menu_task: Task::ready(Ok(())),\n            _pending_update: false,\n            inline_completion: InlineCompletion::default(),\n        }\n    }\n\n    /// Set Input to use multi line mode.\n    ///\n    /// Default rows is 2.\n    pub fn multi_line(mut self, multi_line: bool) -> Self {\n        self.mode = self.mode.multi_line(multi_line);\n        self\n    }\n\n    /// Set Input to use [`InputMode::AutoGrow`] mode with min, max rows limit.\n    pub fn auto_grow(mut self, min_rows: usize, max_rows: usize) -> Self {\n        self.mode = InputMode::auto_grow(min_rows, max_rows);\n        self\n    }\n\n    /// Set Input to use [`InputMode::CodeEditor`] mode.\n    ///\n    /// Default options:\n    ///\n    /// - line_number: true\n    /// - tab_size: 2\n    /// - hard_tabs: false\n    /// - height: 100%\n    /// - multi_line: true\n    /// - indent_guides: true\n    ///\n    /// If `highlighter` is None, will use the default highlighter.\n    ///\n    /// Code Editor aim for help used to simple code editing or display, not a full-featured code editor.\n    ///\n    /// ## Features\n    ///\n    /// - Syntax Highlighting\n    /// - Auto Indent\n    /// - Line Number\n    /// - Large Text support, up to 50K lines.\n    pub fn code_editor(mut self, language: impl Into<SharedString>) -> Self {\n        let language: SharedString = language.into();\n        self.mode = InputMode::code_editor(language);\n        self.searchable = true;\n        self\n    }\n\n    /// Set this input is searchable, default is false (Default true for Code Editor).\n    pub fn searchable(mut self, searchable: bool) -> Self {\n        debug_assert!(self.mode.is_multi_line());\n        self.searchable = searchable;\n        self\n    }\n\n    /// Set placeholder\n    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {\n        self.placeholder = placeholder.into();\n        self\n    }\n\n    /// Set enable/disable code folding, only for [`InputMode::CodeEditor`] mode.\n    ///\n    /// Default: true\n    pub fn folding(mut self, folding: bool) -> Self {\n        debug_assert!(self.mode.is_code_editor());\n        if let InputMode::CodeEditor { folding: f, .. } = &mut self.mode {\n            *f = folding;\n        }\n        self\n    }\n\n    /// Set code folding at runtime, only for [`InputMode::CodeEditor`] mode.\n    ///\n    /// When disabling, all existing folds are cleared.\n    pub fn set_folding(&mut self, folding: bool, _: &mut Window, cx: &mut Context<Self>) {\n        debug_assert!(self.mode.is_code_editor());\n        if let InputMode::CodeEditor { folding: f, .. } = &mut self.mode {\n            *f = folding;\n        }\n        if !folding {\n            self.display_map.clear_folds();\n        }\n        cx.notify();\n    }\n\n    /// Set enable/disable line number, only for [`InputMode::CodeEditor`] mode.\n    pub fn line_number(mut self, line_number: bool) -> Self {\n        debug_assert!(self.mode.is_code_editor() && self.mode.is_multi_line());\n        if let InputMode::CodeEditor { line_number: l, .. } = &mut self.mode {\n            *l = line_number;\n        }\n        self\n    }\n\n    /// Set line number, only for [`InputMode::CodeEditor`] mode.\n    pub fn set_line_number(&mut self, line_number: bool, _: &mut Window, cx: &mut Context<Self>) {\n        debug_assert!(self.mode.is_code_editor() && self.mode.is_multi_line());\n        if let InputMode::CodeEditor { line_number: l, .. } = &mut self.mode {\n            *l = line_number;\n        }\n        cx.notify();\n    }\n\n    /// Set the number of rows for the multi-line Textarea.\n    ///\n    /// This is only used when `multi_line` is set to true.\n    ///\n    /// default: 2\n    pub fn rows(mut self, rows: usize) -> Self {\n        match &mut self.mode {\n            InputMode::PlainText { rows: r, .. } | InputMode::CodeEditor { rows: r, .. } => {\n                *r = rows\n            }\n            InputMode::AutoGrow {\n                max_rows: max_r,\n                rows: r,\n                ..\n            } => {\n                *r = rows;\n                *max_r = rows;\n            }\n        }\n        self\n    }\n\n    /// Set highlighter language for for [`InputMode::CodeEditor`] mode.\n    pub fn set_highlighter(\n        &mut self,\n        new_language: impl Into<SharedString>,\n        cx: &mut Context<Self>,\n    ) {\n        match &mut self.mode {\n            InputMode::CodeEditor {\n                language,\n                highlighter,\n                parse_task,\n                ..\n            } => {\n                *language = new_language.into();\n                *highlighter.borrow_mut() = None;\n                parse_task.borrow_mut().take();\n            }\n            _ => {}\n        }\n        cx.notify();\n    }\n\n    fn reset_highlighter(&mut self, cx: &mut Context<Self>) {\n        match &mut self.mode {\n            InputMode::CodeEditor {\n                highlighter,\n                parse_task,\n                ..\n            } => {\n                *highlighter.borrow_mut() = None;\n                parse_task.borrow_mut().take();\n            }\n            _ => {}\n        }\n        cx.notify();\n    }\n\n    #[inline]\n    pub fn diagnostics(&self) -> Option<&DiagnosticSet> {\n        self.mode.diagnostics()\n    }\n\n    #[inline]\n    pub fn diagnostics_mut(&mut self) -> Option<&mut DiagnosticSet> {\n        self.mode.diagnostics_mut()\n    }\n\n    /// Set placeholder\n    pub fn set_placeholder(\n        &mut self,\n        placeholder: impl Into<SharedString>,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.placeholder = placeholder.into();\n        cx.notify();\n    }\n\n    /// Find which line and sub-line the given offset belongs to, along with the position within that sub-line.\n    ///\n    /// Returns:\n    ///\n    /// - The index of the line (zero-based) containing the offset.\n    /// - The index of the sub-line (zero-based) within the line containing the offset.\n    /// - The position of the offset.\n    pub(super) fn line_and_position_for_offset(\n        &self,\n        offset: usize,\n    ) -> (usize, usize, Option<Point<Pixels>>) {\n        let Some(last_layout) = &self.last_layout else {\n            return (0, 0, None);\n        };\n        let line_height = last_layout.line_height;\n\n        let mut y_offset = last_layout.visible_top;\n        for (vi, line) in last_layout.lines.iter().enumerate() {\n            let prev_lines_offset = last_layout.visible_line_byte_offsets[vi];\n            let local_offset = offset.saturating_sub(prev_lines_offset);\n            if let Some(pos) = line.position_for_index(local_offset, last_layout) {\n                let sub_line_index = (pos.y / line_height) as usize;\n                let adjusted_pos = point(pos.x + last_layout.line_number_width, pos.y + y_offset);\n                return (vi, sub_line_index, Some(adjusted_pos));\n            }\n\n            y_offset += line.size(line_height).height;\n        }\n        (0, 0, None)\n    }\n\n    /// Set the text of the input field.\n    ///\n    /// And the selection_range will be reset to 0..0.\n    pub fn set_value(\n        &mut self,\n        value: impl Into<SharedString>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.history.ignore = true;\n        self.replace_text(value, window, cx);\n        self.history.ignore = false;\n\n        // Ensure cursor to start when set text\n        if self.mode.is_single_line() {\n            self.selected_range = (self.text.len()..self.text.len()).into();\n        } else {\n            self.selected_range.clear();\n        }\n\n        if self.mode.is_code_editor() {\n            self._pending_update = true;\n            self.lsp.reset();\n        }\n\n        // Move scroll to top\n        self.scroll_handle.set_offset(point(px(0.), px(0.)));\n\n        cx.notify();\n    }\n\n    /// Insert text at the current cursor position.\n    ///\n    /// And the cursor will be moved to the end of inserted text.\n    pub fn insert(\n        &mut self,\n        text: impl Into<SharedString>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let was_disabled = self.disabled;\n        self.disabled = false;\n        let text: SharedString = text.into();\n        let range_utf16 = self.range_to_utf16(&(self.cursor()..self.cursor()));\n        self.replace_text_in_range_silent(Some(range_utf16), &text, window, cx);\n        self.selected_range = (self.selected_range.end..self.selected_range.end).into();\n        self.disabled = was_disabled;\n    }\n\n    /// Replace text at the current cursor position.\n    ///\n    /// And the cursor will be moved to the end of replaced text.\n    pub fn replace(\n        &mut self,\n        text: impl Into<SharedString>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let was_disabled = self.disabled;\n        self.disabled = false;\n        let text: SharedString = text.into();\n        self.replace_text_in_range_silent(None, &text, window, cx);\n        self.selected_range = (self.selected_range.end..self.selected_range.end).into();\n        self.disabled = was_disabled;\n    }\n\n    fn replace_text(\n        &mut self,\n        text: impl Into<SharedString>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let was_disabled = self.disabled;\n        self.disabled = false;\n        let text: SharedString = text.into();\n        let range = 0..self.text.chars().map(|c| c.len_utf16()).sum();\n        self.replace_text_in_range_silent(Some(range), &text, window, cx);\n        self.reset_highlighter(cx);\n        self.disabled = was_disabled;\n    }\n\n    /// Set with disabled mode.\n    ///\n    /// See also: [`Self::set_disabled`], [`Self::is_disabled`].\n    #[allow(unused)]\n    pub(crate) fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    /// Set with password masked state.\n    ///\n    /// Only for [`InputMode::SingleLine`] mode.\n    pub fn masked(mut self, masked: bool) -> Self {\n        debug_assert!(self.mode.is_single_line());\n        self.masked = masked;\n        self\n    }\n\n    /// Set the password masked state of the input field.\n    ///\n    /// Only for [`InputMode::SingleLine`] mode.\n    pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {\n        debug_assert!(self.mode.is_single_line());\n        self.masked = masked;\n        cx.notify();\n    }\n\n    /// Set true to clear the input by pressing Escape key.\n    pub fn clean_on_escape(mut self) -> Self {\n        self.clean_on_escape = true;\n        self\n    }\n\n    /// Set the soft wrap mode for multi-line input, default is true.\n    pub fn soft_wrap(mut self, wrap: bool) -> Self {\n        debug_assert!(self.mode.is_multi_line());\n        self.soft_wrap = wrap;\n        self\n    }\n\n    /// Set whether to show whitespace characters.\n    pub fn show_whitespaces(mut self, show: bool) -> Self {\n        self.show_whitespaces = show;\n        self\n    }\n\n    /// Update the soft wrap mode for multi-line input, default is true.\n    pub fn set_soft_wrap(&mut self, wrap: bool, _: &mut Window, cx: &mut Context<Self>) {\n        debug_assert!(self.mode.is_multi_line());\n        self.soft_wrap = wrap;\n        if wrap {\n            let wrap_width = self\n                .last_layout\n                .as_ref()\n                .and_then(|b| b.wrap_width)\n                .unwrap_or(self.input_bounds.size.width);\n\n            self.display_map.on_layout_changed(Some(wrap_width), cx);\n\n            // Reset scroll to left 0\n            let mut offset = self.scroll_handle.offset();\n            offset.x = px(0.);\n            self.scroll_handle.set_offset(offset);\n        } else {\n            self.display_map.on_layout_changed(None, cx);\n        }\n        cx.notify();\n    }\n\n    /// Update whether to show whitespace characters.\n    pub fn set_show_whitespaces(&mut self, show: bool, _: &mut Window, cx: &mut Context<Self>) {\n        self.show_whitespaces = show;\n        cx.notify();\n    }\n\n    /// Set the regular expression pattern of the input field.\n    ///\n    /// Only for [`InputMode::SingleLine`] mode.\n    pub fn pattern(mut self, pattern: regex::Regex) -> Self {\n        debug_assert!(self.mode.is_single_line());\n        self.pattern = Some(pattern);\n        self\n    }\n\n    /// Set the regular expression pattern of the input field with reference.\n    ///\n    /// Only for [`InputMode::SingleLine`] mode.\n    pub fn set_pattern(\n        &mut self,\n        pattern: regex::Regex,\n        _window: &mut Window,\n        _cx: &mut Context<Self>,\n    ) {\n        debug_assert!(self.mode.is_single_line());\n        self.pattern = Some(pattern);\n    }\n\n    /// Set the validation function of the input field.\n    ///\n    /// Only for [`InputMode::SingleLine`] mode.\n    pub fn validate(mut self, f: impl Fn(&str, &mut Context<Self>) -> bool + 'static) -> Self {\n        debug_assert!(self.mode.is_single_line());\n        self.validate = Some(Box::new(f));\n        self\n    }\n\n    /// Set true to show spinner at the input right.\n    ///\n    /// Only for [`InputMode::SingleLine`] mode.\n    pub fn set_loading(&mut self, loading: bool, _: &mut Window, cx: &mut Context<Self>) {\n        debug_assert!(self.mode.is_single_line());\n        self.loading = loading;\n        cx.notify();\n    }\n\n    /// Set the default value of the input field.\n    pub fn default_value(mut self, value: impl Into<SharedString>) -> Self {\n        let text: SharedString = value.into();\n        self.text = Rope::from(text.as_str());\n        if let Some(diagnostics) = self.mode.diagnostics_mut() {\n            diagnostics.reset(&self.text)\n        }\n        // Note: We can't call display_map.set_text here because it needs cx.\n        // The text will be set during prepare_if_need in element.rs\n        self._pending_update = true;\n        self\n    }\n\n    /// Return the value of the input field.\n    pub fn value(&self) -> SharedString {\n        SharedString::new(self.text.to_string())\n    }\n\n    /// Return the portion of the value within the input field that\n    /// is selected by the user\n    pub fn selected_value(&self) -> SharedString {\n        SharedString::new(self.selected_text().to_string())\n    }\n\n    /// Return the value without mask.\n    pub fn unmask_value(&self) -> SharedString {\n        self.mask_pattern.unmask(&self.text.to_string()).into()\n    }\n\n    /// Return the text [`Rope`] of the input field.\n    pub fn text(&self) -> &Rope {\n        &self.text\n    }\n\n    /// Return the (0-based) [`Position`] of the cursor.\n    pub fn cursor_position(&self) -> Position {\n        let offset = self.cursor();\n        self.text.offset_to_position(offset)\n    }\n\n    /// Set (0-based) [`Position`] of the cursor.\n    ///\n    /// This will move the cursor to the specified line and column, and update the selection range.\n    pub fn set_cursor_position(\n        &mut self,\n        position: impl Into<Position>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let position: Position = position.into();\n        let offset = self.text.position_to_offset(&position);\n\n        self.move_to(offset, None, cx);\n        self.update_preferred_column();\n        self.focus(window, cx);\n    }\n\n    /// Focus the input field.\n    pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {\n        self.focus_handle.focus(window, cx);\n        self.blink_cursor.update(cx, |cursor, cx| {\n            cursor.start(cx);\n        });\n    }\n\n    pub(super) fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {\n        self.select_to(self.previous_boundary(self.cursor()), cx);\n    }\n\n    pub(super) fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {\n        self.select_to(self.next_boundary(self.cursor()), cx);\n    }\n\n    pub(super) fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {\n        if self.mode.is_single_line() {\n            return;\n        }\n        let offset = self.start_of_line().saturating_sub(1);\n        self.select_to(self.previous_boundary(offset), cx);\n    }\n\n    pub(super) fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {\n        if self.mode.is_single_line() {\n            return;\n        }\n        let offset = (self.end_of_line() + 1).min(self.text.len());\n        self.select_to(self.next_boundary(offset), cx);\n    }\n\n    pub(super) fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) {\n        self.selected_range = (0..self.text.len()).into();\n        cx.notify();\n    }\n\n    pub(super) fn select_to_start(\n        &mut self,\n        _: &SelectToStart,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.select_to(0, cx);\n    }\n\n    pub(super) fn select_to_end(\n        &mut self,\n        _: &SelectToEnd,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let end = self.text.len();\n        self.select_to(end, cx);\n    }\n\n    pub(super) fn select_to_start_of_line(\n        &mut self,\n        _: &SelectToStartOfLine,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let offset = self.start_of_line();\n        self.select_to(offset, cx);\n    }\n\n    pub(super) fn select_to_end_of_line(\n        &mut self,\n        _: &SelectToEndOfLine,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let offset = self.end_of_line();\n        self.select_to(offset, cx);\n    }\n\n    pub(super) fn select_to_previous_word(\n        &mut self,\n        _: &SelectToPreviousWordStart,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let offset = self.previous_start_of_word();\n        self.select_to(offset, cx);\n    }\n\n    pub(super) fn select_to_next_word(\n        &mut self,\n        _: &SelectToNextWordEnd,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let offset = self.next_end_of_word();\n        self.select_to(offset, cx);\n    }\n\n    /// Return the start offset of the previous word.\n    pub(super) fn previous_start_of_word(&mut self) -> usize {\n        let offset = self.selected_range.start;\n        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));\n        // FIXME: Avoid to_string\n        let left_part = self.text.slice(0..offset).to_string();\n\n        UnicodeSegmentation::split_word_bound_indices(left_part.as_str())\n            .rfind(|(_, s)| !s.trim_start().is_empty())\n            .map(|(i, _)| i)\n            .unwrap_or(0)\n    }\n\n    /// Return the next end offset of the next word.\n    pub(super) fn next_end_of_word(&mut self) -> usize {\n        let offset = self.cursor();\n        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));\n        let right_part = self.text.slice(offset..self.text.len()).to_string();\n\n        UnicodeSegmentation::split_word_bound_indices(right_part.as_str())\n            .find(|(_, s)| !s.trim_start().is_empty())\n            .map(|(i, s)| offset + i + s.len())\n            .unwrap_or(self.text.len())\n    }\n\n    /// Get start of line byte offset of cursor\n    pub(super) fn start_of_line(&self) -> usize {\n        if self.mode.is_single_line() {\n            return 0;\n        }\n\n        let row = self.text.offset_to_point(self.cursor()).row;\n        self.text.line_start_offset(row)\n    }\n\n    /// Get end of line byte offset of cursor\n    pub(super) fn end_of_line(&self) -> usize {\n        if self.mode.is_single_line() {\n            return self.text.len();\n        }\n\n        let row = self.text.offset_to_point(self.cursor()).row;\n        self.text.line_end_offset(row)\n    }\n\n    /// Get start line of selection start or end (The min value).\n    ///\n    /// This is means is always get the first line of selection.\n    pub(super) fn start_of_line_of_selection(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> usize {\n        if self.mode.is_single_line() {\n            return 0;\n        }\n\n        let mut offset =\n            self.previous_boundary(self.selected_range.start.min(self.selected_range.end));\n        if self.text.char_at(offset) == Some('\\r') {\n            offset += 1;\n        }\n\n        let line = self\n            .text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, window, cx)\n            .unwrap_or_default()\n            .rfind('\\n')\n            .map(|i| i + 1)\n            .unwrap_or(0);\n        line\n    }\n\n    /// Get indent string of next line.\n    ///\n    /// To get current and next line indent, to return more depth one.\n    pub(super) fn indent_of_next_line(&mut self) -> String {\n        if self.mode.is_single_line() {\n            return \"\".into();\n        }\n\n        let mut current_indent = String::new();\n        let mut next_indent = String::new();\n        let current_line_start_pos = self.start_of_line();\n        let next_line_start_pos = self.end_of_line();\n        for c in self.text.slice(current_line_start_pos..).chars() {\n            if !c.is_whitespace() {\n                break;\n            }\n            if c == '\\n' || c == '\\r' {\n                break;\n            }\n            current_indent.push(c);\n        }\n\n        for c in self.text.slice(next_line_start_pos..).chars() {\n            if !c.is_whitespace() {\n                break;\n            }\n            if c == '\\n' || c == '\\r' {\n                break;\n            }\n            next_indent.push(c);\n        }\n\n        if next_indent.len() > current_indent.len() {\n            return next_indent;\n        } else {\n            return current_indent;\n        }\n    }\n\n    pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {\n        if self.selected_range.is_empty() {\n            self.select_to(self.previous_boundary(self.cursor()), cx)\n        }\n        self.replace_text_in_range(None, \"\", window, cx);\n        self.pause_blink_cursor(cx);\n    }\n\n    pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {\n        if self.selected_range.is_empty() {\n            self.select_to(self.next_boundary(self.cursor()), cx)\n        }\n        self.replace_text_in_range(None, \"\", window, cx);\n        self.pause_blink_cursor(cx);\n    }\n\n    pub(super) fn delete_to_beginning_of_line(\n        &mut self,\n        _: &DeleteToBeginningOfLine,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if !self.selected_range.is_empty() {\n            self.replace_text_in_range(None, \"\", window, cx);\n            self.pause_blink_cursor(cx);\n            return;\n        }\n\n        let mut offset = self.start_of_line();\n        if offset == self.cursor() {\n            offset = offset.saturating_sub(1);\n        }\n        self.replace_text_in_range_silent(\n            Some(self.range_to_utf16(&(offset..self.cursor()))),\n            \"\",\n            window,\n            cx,\n        );\n        self.pause_blink_cursor(cx);\n    }\n\n    pub(super) fn delete_to_end_of_line(\n        &mut self,\n        _: &DeleteToEndOfLine,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if !self.selected_range.is_empty() {\n            self.replace_text_in_range(None, \"\", window, cx);\n            self.pause_blink_cursor(cx);\n            return;\n        }\n\n        let mut offset = self.end_of_line();\n        if offset == self.cursor() {\n            offset = (offset + 1).clamp(0, self.text.len());\n        }\n        self.replace_text_in_range_silent(\n            Some(self.range_to_utf16(&(self.cursor()..offset))),\n            \"\",\n            window,\n            cx,\n        );\n        self.pause_blink_cursor(cx);\n    }\n\n    pub(super) fn delete_previous_word(\n        &mut self,\n        _: &DeleteToPreviousWordStart,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if !self.selected_range.is_empty() {\n            self.replace_text_in_range(None, \"\", window, cx);\n            self.pause_blink_cursor(cx);\n            return;\n        }\n\n        let offset = self.previous_start_of_word();\n        self.replace_text_in_range_silent(\n            Some(self.range_to_utf16(&(offset..self.cursor()))),\n            \"\",\n            window,\n            cx,\n        );\n        self.pause_blink_cursor(cx);\n    }\n\n    pub(super) fn delete_next_word(\n        &mut self,\n        _: &DeleteToNextWordEnd,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if !self.selected_range.is_empty() {\n            self.replace_text_in_range(None, \"\", window, cx);\n            self.pause_blink_cursor(cx);\n            return;\n        }\n\n        let offset = self.next_end_of_word();\n        self.replace_text_in_range_silent(\n            Some(self.range_to_utf16(&(self.cursor()..offset))),\n            \"\",\n            window,\n            cx,\n        );\n        self.pause_blink_cursor(cx);\n    }\n\n    pub(super) fn enter(&mut self, action: &Enter, window: &mut Window, cx: &mut Context<Self>) {\n        if self.handle_action_for_context_menu(Box::new(action.clone()), window, cx) {\n            return;\n        }\n\n        // Clear inline completion on enter (user chose not to accept it)\n        if self.has_inline_completion() {\n            self.clear_inline_completion(cx);\n        }\n\n        if self.mode.is_multi_line() {\n            // Get current line indent\n            let indent = if self.mode.is_code_editor() {\n                self.indent_of_next_line()\n            } else {\n                \"\".to_string()\n            };\n\n            // Add newline and indent\n            let new_line_text = format!(\"\\n{}\", indent);\n            self.replace_text_in_range_silent(None, &new_line_text, window, cx);\n            self.pause_blink_cursor(cx);\n        } else {\n            // Single line input, just emit the event (e.g.: In a dialog to confirm).\n            cx.propagate();\n        }\n\n        cx.emit(InputEvent::PressEnter {\n            secondary: action.secondary,\n        });\n    }\n\n    pub(super) fn clean(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        self.replace_text(\"\", window, cx);\n        self.selected_range = (0..0).into();\n        self.scroll_to(0, None, cx);\n    }\n\n    pub(super) fn escape(&mut self, action: &Escape, window: &mut Window, cx: &mut Context<Self>) {\n        if self.handle_action_for_context_menu(Box::new(action.clone()), window, cx) {\n            return;\n        }\n\n        // Clear inline completion on escape\n        if self.has_inline_completion() {\n            self.clear_inline_completion(cx);\n            return; // Consume the escape, don't propagate\n        }\n\n        if self.ime_marked_range.is_some() {\n            self.unmark_text(window, cx);\n        }\n\n        if self.clean_on_escape {\n            return self.clean(window, cx);\n        }\n\n        cx.propagate();\n    }\n\n    pub(super) fn on_mouse_down(\n        &mut self,\n        event: &MouseDownEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        // Clear inline completion on any mouse interaction\n        self.clear_inline_completion(cx);\n\n        // If there have IME marked range and is empty (Means pressed Esc to abort IME typing)\n        // Clear the marked range.\n        if let Some(ime_marked_range) = &self.ime_marked_range {\n            if ime_marked_range.len() == 0 {\n                self.ime_marked_range = None;\n            }\n        }\n\n        self.selecting = true;\n        let offset = self.index_for_mouse_position(event.position);\n\n        if self.handle_click_hover_definition(event, offset, window, cx) {\n            return;\n        }\n\n        // Triple click to select line\n        if event.button == MouseButton::Left && event.click_count >= 3 {\n            self.select_line(offset, window, cx);\n            return;\n        }\n\n        // Double click to select word\n        if event.button == MouseButton::Left && event.click_count == 2 {\n            self.select_word(offset, window, cx);\n            return;\n        }\n\n        // Show Mouse context menu\n        if event.button == MouseButton::Right {\n            self.handle_right_click_menu(event, offset, window, cx);\n            return;\n        }\n\n        if event.modifiers.shift {\n            self.select_to(offset, cx);\n        } else {\n            self.move_to(offset, None, cx)\n        }\n    }\n\n    pub(super) fn on_mouse_up(\n        &mut self,\n        _: &MouseUpEvent,\n        _window: &mut Window,\n        _cx: &mut Context<Self>,\n    ) {\n        if self.selected_range.is_empty() {\n            self.selection_reversed = false;\n        }\n        self.selecting = false;\n        self.selected_word_range = None;\n    }\n\n    pub(super) fn on_mouse_move(\n        &mut self,\n        event: &MouseMoveEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        // Show diagnostic popover on mouse move\n        let offset = self.index_for_mouse_position(event.position);\n        self.handle_mouse_move(offset, event, window, cx);\n\n        if self.mode.is_code_editor() {\n            if let Some(diagnostic) = self\n                .mode\n                .diagnostics()\n                .and_then(|set| set.for_offset(offset))\n            {\n                if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() {\n                    if diagnostic_popover.read(cx).diagnostic.range == diagnostic.range {\n                        diagnostic_popover.update(cx, |this, cx| {\n                            this.show(cx);\n                        });\n\n                        return;\n                    }\n                }\n\n                self.diagnostic_popover = Some(DiagnosticPopover::new(diagnostic, cx.entity(), cx));\n                cx.notify();\n            } else {\n                if let Some(diagnostic_popover) = self.diagnostic_popover.as_mut() {\n                    diagnostic_popover.update(cx, |this, cx| {\n                        this.check_to_hide(event.position, cx);\n                    })\n                }\n            }\n        }\n    }\n\n    pub(super) fn on_scroll_wheel(\n        &mut self,\n        event: &ScrollWheelEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let line_height = self\n            .last_layout\n            .as_ref()\n            .map(|layout| layout.line_height)\n            .unwrap_or(window.line_height());\n        let delta = event.delta.pixel_delta(line_height);\n\n        let old_offset = self.scroll_handle.offset();\n        self.update_scroll_offset(Some(old_offset + delta), cx);\n\n        // Only stop propagation if the offset actually changed\n        if self.scroll_handle.offset() != old_offset {\n            cx.stop_propagation();\n        }\n\n        self.diagnostic_popover = None;\n    }\n\n    pub(super) fn update_scroll_offset(\n        &mut self,\n        offset: Option<Point<Pixels>>,\n        cx: &mut Context<Self>,\n    ) {\n        let mut offset = offset.unwrap_or(self.scroll_handle.offset());\n        // In addition to left alignment, a cursor position will be reserved on the right side\n        let safe_x_offset = if self.text_align == TextAlign::Left {\n            px(0.)\n        } else {\n            -CURSOR_WIDTH\n        };\n\n        let safe_y_range =\n            (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);\n        let safe_x_range = (-self.scroll_size.width + self.input_bounds.size.width + safe_x_offset)\n            .min(safe_x_offset)..px(0.);\n\n        offset.y = if self.mode.is_single_line() {\n            px(0.)\n        } else {\n            offset.y.clamp(safe_y_range.start, safe_y_range.end)\n        };\n        offset.x = offset.x.clamp(safe_x_range.start, safe_x_range.end);\n        self.scroll_handle.set_offset(offset);\n        cx.notify();\n    }\n\n    /// Scroll to make the given offset visible.\n    ///\n    /// If `direction` is Some, will keep edges at the same side.\n    pub(crate) fn scroll_to(\n        &mut self,\n        offset: usize,\n        direction: Option<MoveDirection>,\n        cx: &mut Context<Self>,\n    ) {\n        let Some(last_layout) = self.last_layout.as_ref() else {\n            return;\n        };\n        let Some(bounds) = self.last_bounds.as_ref() else {\n            return;\n        };\n\n        let mut scroll_offset = self.scroll_handle.offset();\n        let was_offset = scroll_offset;\n        let line_height = last_layout.line_height;\n\n        let point = self.text.offset_to_point(offset);\n\n        let row = point.row;\n\n        let mut row_offset_y = px(0.);\n        for (ix, _wrap_line) in self.display_map.lines().iter().enumerate() {\n            if ix == row {\n                break;\n            }\n\n            // Only accumulate height for visible (non-folded) wrap rows\n            let visible_wrap_rows = self.display_map.visible_wrap_row_count_for_buffer_line(ix);\n            row_offset_y += line_height * visible_wrap_rows;\n        }\n\n        // Apart from left alignment, just leave enough space for the cursor size on the right side.\n        let safety_margin = if last_layout.text_align == TextAlign::Left {\n            RIGHT_MARGIN\n        } else {\n            CURSOR_WIDTH\n        };\n        if let Some(line) = last_layout\n            .lines\n            .get(row.saturating_sub(last_layout.visible_range.start))\n        {\n            // Check to scroll horizontally and soft wrap lines\n            if let Some(pos) = line.position_for_index(point.column, last_layout) {\n                let bounds_width = bounds.size.width - last_layout.line_number_width;\n                let col_offset_x = pos.x;\n                row_offset_y += pos.y;\n                if col_offset_x - safety_margin < -scroll_offset.x {\n                    // If the position is out of the visible area, scroll to make it visible\n                    scroll_offset.x = -col_offset_x + safety_margin;\n                } else if col_offset_x + safety_margin > -scroll_offset.x + bounds_width {\n                    scroll_offset.x = -(col_offset_x - bounds_width + safety_margin);\n                }\n            }\n        }\n\n        // Check if row_offset_y is out of the viewport\n        // If row offset is not in the viewport, scroll to make it visible\n        let edge_height = if direction.is_some() && self.mode.is_code_editor() {\n            3 * line_height\n        } else {\n            line_height\n        };\n        if row_offset_y - edge_height + line_height < -scroll_offset.y {\n            // Scroll up\n            scroll_offset.y = -row_offset_y + edge_height - line_height;\n        } else if row_offset_y + edge_height > -scroll_offset.y + bounds.size.height {\n            // Scroll down\n            scroll_offset.y = -(row_offset_y - bounds.size.height + edge_height);\n        }\n\n        // Avoid necessary scroll, when it was already in the correct position.\n        if direction == Some(MoveDirection::Up) {\n            scroll_offset.y = scroll_offset.y.max(was_offset.y);\n        } else if direction == Some(MoveDirection::Down) {\n            scroll_offset.y = scroll_offset.y.min(was_offset.y);\n        }\n\n        scroll_offset.x = scroll_offset.x.min(px(0.));\n        scroll_offset.y = scroll_offset.y.min(px(0.));\n        self.deferred_scroll_offset = Some(scroll_offset);\n        cx.notify();\n    }\n\n    pub(super) fn show_character_palette(\n        &mut self,\n        _: &ShowCharacterPalette,\n        window: &mut Window,\n        _: &mut Context<Self>,\n    ) {\n        window.show_character_palette();\n    }\n\n    pub(super) fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {\n        if self.selected_range.is_empty() {\n            return;\n        }\n\n        let selected_text = self.text.slice(self.selected_range).to_string();\n        cx.write_to_clipboard(ClipboardItem::new_string(selected_text));\n    }\n\n    pub(super) fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {\n        if self.selected_range.is_empty() {\n            return;\n        }\n\n        let selected_text = self.text.slice(self.selected_range).to_string();\n        cx.write_to_clipboard(ClipboardItem::new_string(selected_text));\n\n        self.replace_text_in_range_silent(None, \"\", window, cx);\n    }\n\n    pub(super) fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {\n        if let Some(clipboard) = cx.read_from_clipboard() {\n            let mut new_text = clipboard.text().unwrap_or_default();\n            if !self.mode.is_multi_line() {\n                new_text = new_text.replace('\\n', \"\");\n            }\n\n            self.replace_text_in_range_silent(None, &new_text, window, cx);\n            self.scroll_to(self.cursor(), None, cx);\n        }\n    }\n\n    fn push_history(&mut self, text: &Rope, range: &Range<usize>, new_text: &str) {\n        if self.history.ignore {\n            return;\n        }\n\n        let range =\n            text.clip_offset(range.start, Bias::Left)..text.clip_offset(range.end, Bias::Right);\n        let old_text = text.slice(range.clone()).to_string();\n        let new_range = range.start..range.start + new_text.len();\n\n        self.history\n            .push(Change::new(range, &old_text, new_range, new_text));\n    }\n\n    pub(super) fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {\n        self.history.ignore = true;\n        if let Some(changes) = self.history.undo() {\n            for change in changes {\n                let range_utf16 = self.range_to_utf16(&change.new_range.into());\n                self.replace_text_in_range_silent(Some(range_utf16), &change.old_text, window, cx);\n            }\n        }\n        self.history.ignore = false;\n    }\n\n    pub(super) fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {\n        self.history.ignore = true;\n        if let Some(changes) = self.history.redo() {\n            for change in changes {\n                let range_utf16 = self.range_to_utf16(&change.old_range.into());\n                self.replace_text_in_range_silent(Some(range_utf16), &change.new_text, window, cx);\n            }\n        }\n        self.history.ignore = false;\n    }\n\n    /// Get byte offset of the cursor.\n    ///\n    /// The offset is the UTF-8 offset.\n    pub fn cursor(&self) -> usize {\n        if let Some(ime_marked_range) = &self.ime_marked_range {\n            return ime_marked_range.end;\n        }\n\n        if self.selection_reversed {\n            self.selected_range.start\n        } else {\n            self.selected_range.end\n        }\n    }\n\n    pub(crate) fn index_for_mouse_position(&self, position: Point<Pixels>) -> usize {\n        // If the text is empty, always return 0\n        if self.text.len() == 0 {\n            return 0;\n        }\n\n        let (Some(bounds), Some(last_layout)) =\n            (self.last_bounds.as_ref(), self.last_layout.as_ref())\n        else {\n            return 0;\n        };\n\n        let line_height = last_layout.line_height;\n        let line_number_width = last_layout.line_number_width;\n\n        // TIP: About the IBeam cursor\n        //\n        // If cursor style is IBeam, the mouse mouse position is in the middle of the cursor (This is special in OS)\n\n        // The position is relative to the bounds of the text input\n        //\n        // bounds.origin:\n        //\n        // - included the input padding.\n        // - included the scroll offset.\n        let inner_position = position - bounds.origin - point(line_number_width, px(0.));\n\n        let mut y_offset = last_layout.visible_top;\n\n        // Traverse visible buffer lines (compact, no hidden entries)\n        for (vi, (line_layout, _buffer_line)) in last_layout\n            .lines\n            .iter()\n            .zip(last_layout.visible_buffer_lines.iter())\n            .enumerate()\n        {\n            let line_start_offset = last_layout.visible_line_byte_offsets[vi];\n\n            // Calculate line origin for this display row\n            let line_origin = point(px(0.), y_offset);\n            let pos = inner_position - line_origin;\n\n            // Return offset by use closest_index_for_x if is single line mode.\n            if self.mode.is_single_line() {\n                let local_index = line_layout.closest_index_for_x(pos.x, last_layout);\n                let index = line_start_offset + local_index;\n                return if self.masked {\n                    self.text.char_index_to_offset(index)\n                } else {\n                    index.min(self.text.len())\n                };\n            }\n\n            // Check if mouse is in this line's bounds\n            if let Some(local_index) = line_layout.closest_index_for_position(pos, last_layout) {\n                let index = line_start_offset + local_index;\n                return if self.masked {\n                    self.text.char_index_to_offset(index)\n                } else {\n                    index.min(self.text.len())\n                };\n            } else if pos.y < px(0.) {\n                // Mouse is above this line, return start of this line\n                return if self.masked {\n                    self.text.char_index_to_offset(line_start_offset)\n                } else {\n                    line_start_offset\n                };\n            }\n\n            y_offset += line_layout.size(line_height).height;\n        }\n\n        // Mouse is below all visible lines, return end of text\n        let index = self.text.len();\n        if self.masked {\n            self.text.char_index_to_offset(index)\n        } else {\n            index\n        }\n    }\n\n    /// Returns a y offsetted point for the line origin.\n    /// Select the text from the current cursor position to the given offset.\n    ///\n    /// The offset is the UTF-8 offset.\n    ///\n    /// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.\n    pub(crate) fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {\n        self.clear_inline_completion(cx);\n\n        let offset = offset.clamp(0, self.text.len());\n        if self.selection_reversed {\n            self.selected_range.start = offset\n        } else {\n            self.selected_range.end = offset\n        };\n\n        if self.selected_range.end < self.selected_range.start {\n            self.selection_reversed = !self.selection_reversed;\n            self.selected_range = (self.selected_range.end..self.selected_range.start).into();\n        }\n\n        // Ensure keep word selected range\n        if let Some(word_range) = self.selected_word_range.as_ref() {\n            if self.selected_range.start > word_range.start {\n                self.selected_range.start = word_range.start;\n            }\n            if self.selected_range.end < word_range.end {\n                self.selected_range.end = word_range.end;\n            }\n        }\n        if self.selected_range.is_empty() {\n            self.update_preferred_column();\n        }\n        cx.notify()\n    }\n\n    /// Unselects the currently selected text.\n    pub fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        let offset = self.cursor();\n        self.selected_range = (offset..offset).into();\n        cx.notify()\n    }\n\n    #[inline]\n    pub(super) fn offset_from_utf16(&self, offset: usize) -> usize {\n        self.text.offset_utf16_to_offset(offset)\n    }\n\n    #[inline]\n    pub(super) fn offset_to_utf16(&self, offset: usize) -> usize {\n        self.text.offset_to_offset_utf16(offset)\n    }\n\n    #[inline]\n    pub(super) fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {\n        self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)\n    }\n\n    #[inline]\n    pub(super) fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {\n        self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)\n    }\n\n    /// If offset falls on a hidden (folded) line, clamp backward to the end of\n    /// the fold header line (last visible position before the fold).\n    fn clamp_offset_to_visible_backward(&self, offset: usize) -> usize {\n        let line = self.text.offset_to_point(offset).row;\n        if self.display_map.is_buffer_line_hidden(line) {\n            for fold in self.display_map.folded_ranges() {\n                if line > fold.start_line && line <= fold.end_line {\n                    return self.text.line_end_offset(fold.start_line);\n                }\n            }\n        }\n        offset\n    }\n\n    /// If offset falls on a hidden (folded) line, clamp forward to the start of\n    /// the fold end line (first visible position after the fold).\n    fn clamp_offset_to_visible_forward(&self, offset: usize) -> usize {\n        let line = self.text.offset_to_point(offset).row;\n        if self.display_map.is_buffer_line_hidden(line) {\n            for fold in self.display_map.folded_ranges() {\n                if line > fold.start_line && line <= fold.end_line {\n                    return self.text.line_start_offset(fold.end_line);\n                }\n            }\n        }\n        offset\n    }\n\n    pub(super) fn previous_boundary(&self, offset: usize) -> usize {\n        let mut offset = self.text.clip_offset(offset.saturating_sub(1), Bias::Left);\n        if let Some(ch) = self.text.char_at(offset) {\n            if ch == '\\r' {\n                offset -= 1;\n            }\n        }\n\n        self.clamp_offset_to_visible_backward(offset)\n    }\n\n    pub(super) fn next_boundary(&self, offset: usize) -> usize {\n        let mut offset = self.text.clip_offset(offset + 1, Bias::Right);\n        if let Some(ch) = self.text.char_at(offset) {\n            if ch == '\\r' {\n                offset += 1;\n            }\n        }\n\n        self.clamp_offset_to_visible_forward(offset)\n    }\n\n    /// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible.\n    pub(crate) fn show_cursor(&self, window: &Window, cx: &App) -> bool {\n        (self.focus_handle.is_focused(window) || self.is_context_menu_open(cx))\n            && !self.disabled\n            && self.blink_cursor.read(cx).visible()\n            && window.is_window_active()\n    }\n\n    fn on_focus(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        self.blink_cursor.update(cx, |cursor, cx| {\n            cursor.start(cx);\n        });\n        cx.emit(InputEvent::Focus);\n    }\n\n    fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        if self.is_context_menu_open(cx) {\n            return;\n        }\n\n        // NOTE: Do not cancel select, when blur.\n        // Because maybe user want to copy the selected text by AppMenuBar (will take focus handle).\n\n        self.hover_popover = None;\n        self.diagnostic_popover = None;\n        self.context_menu = None;\n        self.clear_inline_completion(cx);\n        self.blink_cursor.update(cx, |cursor, cx| {\n            cursor.stop(cx);\n        });\n        Root::update(window, cx, |root, _, _| {\n            root.focused_input = None;\n        });\n        cx.emit(InputEvent::Blur);\n        cx.notify();\n    }\n\n    pub(super) fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {\n        self.blink_cursor.update(cx, |cursor, cx| {\n            cursor.pause(cx);\n        });\n    }\n\n    pub(super) fn on_key_down(&mut self, _: &KeyDownEvent, _: &mut Window, cx: &mut Context<Self>) {\n        self.pause_blink_cursor(cx);\n    }\n\n    pub(super) fn on_drag_move(\n        &mut self,\n        event: &MouseMoveEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if self.text.len() == 0 {\n            return;\n        }\n\n        if self.last_layout.is_none() {\n            return;\n        }\n\n        if !self.focus_handle.is_focused(window) {\n            return;\n        }\n\n        if !self.selecting {\n            return;\n        }\n\n        let offset = self.index_for_mouse_position(event.position);\n        self.select_to(offset, cx);\n    }\n\n    fn is_valid_input(&self, new_text: &str, cx: &mut Context<Self>) -> bool {\n        if new_text.is_empty() {\n            return true;\n        }\n\n        if let Some(validate) = &self.validate {\n            if !validate(new_text, cx) {\n                return false;\n            }\n        }\n\n        if !self.mask_pattern.is_valid(new_text) {\n            return false;\n        }\n\n        let Some(pattern) = &self.pattern else {\n            return true;\n        };\n\n        pattern.is_match(new_text)\n    }\n\n    /// Set the mask pattern for formatting the input text.\n    ///\n    /// The pattern can contain:\n    /// - 9: Any digit or dot\n    /// - A: Any letter\n    /// - *: Any character\n    /// - Other characters will be treated as literal mask characters\n    ///\n    /// Example: \"(999)999-999\" for phone numbers\n    pub fn mask_pattern(mut self, pattern: impl Into<MaskPattern>) -> Self {\n        self.mask_pattern = pattern.into();\n        if let Some(placeholder) = self.mask_pattern.placeholder() {\n            self.placeholder = placeholder.into();\n        }\n        self\n    }\n\n    pub fn set_mask_pattern(\n        &mut self,\n        pattern: impl Into<MaskPattern>,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.mask_pattern = pattern.into();\n        if let Some(placeholder) = self.mask_pattern.placeholder() {\n            self.placeholder = placeholder.into();\n        }\n        cx.notify();\n    }\n\n    pub(super) fn set_input_bounds(&mut self, new_bounds: Bounds<Pixels>, cx: &mut Context<Self>) {\n        let wrap_width_changed = self.input_bounds.size.width != new_bounds.size.width;\n        self.input_bounds = new_bounds;\n\n        // Update display_map wrap_width if changed.\n        if let Some(last_layout) = self.last_layout.as_ref() {\n            if wrap_width_changed {\n                let wrap_width = if !self.soft_wrap {\n                    // None to disable wrapping (will use Pixels::MAX)\n                    None\n                } else {\n                    last_layout.wrap_width\n                };\n\n                self.display_map.on_layout_changed(wrap_width, cx);\n                self.mode.update_auto_grow(&self.display_map);\n                cx.notify();\n            }\n        }\n    }\n\n    pub(super) fn selected_text(&self) -> RopeSlice<'_> {\n        let range_utf16 = self.range_to_utf16(&self.selected_range.into());\n        let range = self.range_from_utf16(&range_utf16);\n        self.text.slice(range)\n    }\n\n    /// Return the rendered bounds for a UTF-8 byte range in the current input contents.\n    ///\n    /// Returns `None` when the requested range is not currently laid out or visible.\n    pub fn range_to_bounds(&self, range: &Range<usize>) -> Option<Bounds<Pixels>> {\n        let Some(last_layout) = self.last_layout.as_ref() else {\n            return None;\n        };\n\n        let Some(last_bounds) = self.last_bounds else {\n            return None;\n        };\n\n        let (_, _, start_pos) = self.line_and_position_for_offset(range.start);\n        let (_, _, end_pos) = self.line_and_position_for_offset(range.end);\n\n        let Some(start_pos) = start_pos else {\n            return None;\n        };\n        let Some(end_pos) = end_pos else {\n            return None;\n        };\n\n        Some(Bounds::from_corners(\n            last_bounds.origin + start_pos,\n            last_bounds.origin + end_pos + point(px(0.), last_layout.line_height),\n        ))\n    }\n\n    /// Replace text in range in silent.\n    ///\n    /// This will not trigger any UI interaction, such as auto-completion.\n    pub(crate) fn replace_text_in_range_silent(\n        &mut self,\n        range_utf16: Option<Range<usize>>,\n        new_text: &str,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.silent_replace_text = true;\n        self.replace_text_in_range(range_utf16, new_text, window, cx);\n        self.silent_replace_text = false;\n    }\n\n    /// Update fold candidates from tree-sitter syntax tree (full extraction).\n    /// Used only on initial load or language changes.\n    fn update_fold_candidates(&mut self) {\n        if !self.mode.is_folding() {\n            return;\n        }\n\n        let Some(highlighter_rc) = self.mode.highlighter() else {\n            return;\n        };\n\n        let highlighter = highlighter_rc.borrow();\n        let Some(highlighter) = highlighter.as_ref() else {\n            return;\n        };\n\n        let Some(tree) = highlighter.tree() else {\n            return;\n        };\n\n        let fold_ranges = crate::input::display_map::extract_fold_ranges(tree);\n        self.display_map.set_fold_candidates(fold_ranges);\n    }\n\n    /// Incrementally update fold candidates after a text edit.\n    /// Only traverses the edited region of the syntax tree instead of the full tree.\n    fn update_fold_candidates_incremental(&mut self, edit_range: &Range<usize>, new_text: &str) {\n        if !self.mode.is_folding() {\n            return;\n        }\n\n        let Some(highlighter_rc) = self.mode.highlighter() else {\n            return;\n        };\n\n        let highlighter = highlighter_rc.borrow();\n        let Some(highlighter) = highlighter.as_ref() else {\n            return;\n        };\n\n        let Some(tree) = highlighter.tree() else {\n            return;\n        };\n\n        // The new byte range in the updated text after the edit\n        let new_end = edit_range.start + new_text.len();\n        self.display_map.update_fold_candidates_for_edit(\n            tree,\n            edit_range.start..new_end,\n            &self.text,\n        );\n    }\n\n    /// Spawn a background parse after the synchronous parse timed out.\n    ///\n    /// Dropping the returned `Task` (stored in `parse_task`) cancels the\n    /// parse, which naturally debounces rapid edits.\n    #[cfg(not(target_family = \"wasm\"))]\n    fn dispatch_background_parse(\n        pending: super::mode::PendingBackgroundParse,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let highlighter_rc = pending.highlighter;\n        let parse_task_rc = pending.parse_task;\n        let language = pending.language;\n        let text = pending.text;\n\n        let old_tree = highlighter_rc\n            .borrow()\n            .as_ref()\n            .and_then(|h| h.tree().cloned());\n\n        // Extract injection parse data on the main thread before spawning, so that\n        // compute_injection_layers can also run on the background thread.\n        let injection_data = highlighter_rc\n            .borrow()\n            .as_ref()\n            .and_then(|h| h.injection_parse_data());\n\n        let text_for_apply = text.clone();\n        let task = cx.spawn_in(window, async move |entity, cx| {\n            let result = cx\n                .background_executor()\n                .spawn(async move {\n                    let Some(config) = LanguageRegistry::singleton().language(&language) else {\n                        return None;\n                    };\n\n                    let mut parser = tree_sitter::Parser::new();\n                    if parser.set_language(&config.language).is_err() {\n                        return None;\n                    }\n\n                    let new_tree = parser.parse_with_options(\n                        &mut |offset, _| {\n                            if offset >= text.len() {\n                                \"\"\n                            } else {\n                                let (chunk, chunk_byte_ix) = text.chunk(offset);\n                                &chunk[offset - chunk_byte_ix..]\n                            }\n                        },\n                        old_tree.as_ref(),\n                        None,\n                    )?;\n\n                    // Compute injection layers in the background to avoid blocking the\n                    // main thread with combined-injection parsing (e.g. PHP, HTML+JS/CSS).\n                    let injection_layers = if let Some(data) = injection_data {\n                        crate::highlighter::SyntaxHighlighter::compute_injection_layers(\n                            data, &new_tree, &text,\n                        )\n                    } else {\n                        Default::default()\n                    };\n\n                    Some((new_tree, injection_layers))\n                })\n                .await;\n\n            if let Some((new_tree, injection_layers)) = result {\n                if let Some(h) = highlighter_rc.borrow_mut().as_mut() {\n                    h.apply_background_tree(new_tree, &text_for_apply, injection_layers);\n                }\n\n                // Trigger re-render so the new highlights are displayed.\n                _ = entity.update(cx, |_, cx| {\n                    cx.notify();\n                });\n            }\n        });\n\n        parse_task_rc.borrow_mut().replace(task);\n    }\n\n    #[cfg(target_family = \"wasm\")]\n    fn dispatch_background_parse(\n        _pending: super::mode::PendingBackgroundParse,\n        _window: &mut Window,\n        _cx: &mut Context<Self>,\n    ) {\n        // No-op\n    }\n}\n\nimpl EntityInputHandler for InputState {\n    fn text_for_range(\n        &mut self,\n        range_utf16: Range<usize>,\n        adjusted_range: &mut Option<Range<usize>>,\n        _window: &mut Window,\n        _cx: &mut Context<Self>,\n    ) -> Option<String> {\n        let range = self.range_from_utf16(&range_utf16);\n        adjusted_range.replace(self.range_to_utf16(&range));\n        Some(self.text.slice(range).to_string())\n    }\n\n    fn selected_text_range(\n        &mut self,\n        _ignore_disabled_input: bool,\n        _window: &mut Window,\n        _cx: &mut Context<Self>,\n    ) -> Option<UTF16Selection> {\n        Some(UTF16Selection {\n            range: self.range_to_utf16(&self.selected_range.into()),\n            reversed: false,\n        })\n    }\n\n    fn marked_text_range(\n        &self,\n        _window: &mut Window,\n        _cx: &mut Context<Self>,\n    ) -> Option<Range<usize>> {\n        self.ime_marked_range\n            .map(|range| self.range_to_utf16(&range.into()))\n    }\n\n    fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {\n        self.ime_marked_range = None;\n    }\n\n    /// Replace text in range.\n    ///\n    /// - If the new text is invalid, it will not be replaced.\n    /// - If `range_utf16` is not provided, the current selected range will be used.\n    fn replace_text_in_range(\n        &mut self,\n        range_utf16: Option<Range<usize>>,\n        new_text: &str,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if self.disabled {\n            return;\n        }\n\n        self.pause_blink_cursor(cx);\n\n        let range = range_utf16\n            .as_ref()\n            .map(|range_utf16| self.range_from_utf16(range_utf16))\n            .or(self.ime_marked_range.map(|range| {\n                let range = self.range_to_utf16(&(range.start..range.end));\n                self.range_from_utf16(&range)\n            }))\n            .unwrap_or(self.selected_range.into());\n\n        let old_text = self.text.clone();\n        self.text.replace(range.clone(), new_text);\n\n        let mut new_offset = (range.start + new_text.len()).min(self.text.len());\n\n        if self.mode.is_single_line() {\n            let pending_text = self.text.to_string();\n            // Check if the new text is valid\n            if !self.is_valid_input(&pending_text, cx) {\n                self.text = old_text;\n                return;\n            }\n\n            if !self.mask_pattern.is_none() {\n                let mask_text = self.mask_pattern.mask(&pending_text);\n                self.text = Rope::from(mask_text.as_str());\n                let new_text_len =\n                    (new_text.len() + mask_text.len()).saturating_sub(pending_text.len());\n                new_offset = (range.start + new_text_len).min(mask_text.len());\n            }\n        }\n\n        self.push_history(&old_text, &range, &new_text);\n        self.history.end_grouping();\n        if let Some(diagnostics) = self.mode.diagnostics_mut() {\n            diagnostics.reset(&self.text)\n        }\n        // Adjust folds before updating wrap map: remove overlapping folds and shift others\n        self.display_map\n            .adjust_folds_for_edit(&old_text, &range, new_text);\n        self.display_map\n            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);\n\n        let bg = self\n            .mode\n            .update_highlighter(&range, &self.text, &new_text, true, cx);\n        if let Some(bg) = bg {\n            Self::dispatch_background_parse(bg, window, cx);\n        }\n\n        self.update_fold_candidates_incremental(&range, new_text);\n        self.lsp.update(&self.text, window, cx);\n        self.selected_range = (new_offset..new_offset).into();\n        self.ime_marked_range.take();\n        self.update_preferred_column();\n        self.update_search(cx);\n        self.mode.update_auto_grow(&self.display_map);\n        if !self.silent_replace_text {\n            self.handle_completion_trigger(&range, &new_text, window, cx);\n        }\n        cx.emit(InputEvent::Change);\n        cx.notify();\n    }\n\n    /// Mark text is the IME temporary insert on typing.\n    fn replace_and_mark_text_in_range(\n        &mut self,\n        range_utf16: Option<Range<usize>>,\n        new_text: &str,\n        new_selected_range_utf16: Option<Range<usize>>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if self.disabled {\n            return;\n        }\n\n        self.lsp.reset();\n\n        let range = range_utf16\n            .as_ref()\n            .map(|range_utf16| self.range_from_utf16(range_utf16))\n            .or(self.ime_marked_range.map(|range| {\n                let range = self.range_to_utf16(&(range.start..range.end));\n                self.range_from_utf16(&range)\n            }))\n            .unwrap_or(self.selected_range.into());\n\n        let old_text = self.text.clone();\n        self.text.replace(range.clone(), new_text);\n\n        if self.mode.is_single_line() {\n            let pending_text = self.text.to_string();\n            if !self.is_valid_input(&pending_text, cx) {\n                self.text = old_text;\n                return;\n            }\n        }\n\n        if let Some(diagnostics) = self.mode.diagnostics_mut() {\n            diagnostics.reset(&self.text)\n        }\n        // Adjust folds before updating wrap map: remove overlapping folds and shift others\n        self.display_map\n            .adjust_folds_for_edit(&old_text, &range, new_text);\n        self.display_map\n            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);\n\n        let bg = self\n            .mode\n            .update_highlighter(&range, &self.text, &new_text, true, cx);\n        if let Some(bg) = bg {\n            Self::dispatch_background_parse(bg, window, cx);\n        }\n\n        self.update_fold_candidates_incremental(&range, new_text);\n        self.lsp.update(&self.text, window, cx);\n        if new_text.is_empty() {\n            // Cancel selection, when cancel IME input.\n            self.selected_range = (range.start..range.start).into();\n            self.ime_marked_range = None;\n        } else {\n            self.ime_marked_range = Some((range.start..range.start + new_text.len()).into());\n            self.selected_range = new_selected_range_utf16\n                .as_ref()\n                .map(|range_utf16| self.range_from_utf16(range_utf16))\n                .map(|new_range| new_range.start + range.start..new_range.end + range.end)\n                .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len())\n                .into();\n        }\n        self.mode.update_auto_grow(&self.display_map);\n        self.history.start_grouping();\n        self.push_history(&old_text, &range, new_text);\n        cx.notify();\n    }\n\n    /// Used to position IME candidates.\n    fn bounds_for_range(\n        &mut self,\n        range_utf16: Range<usize>,\n        bounds: Bounds<Pixels>,\n        _window: &mut Window,\n        _cx: &mut Context<Self>,\n    ) -> Option<Bounds<Pixels>> {\n        let last_layout = self.last_layout.as_ref()?;\n        let line_height = last_layout.line_height;\n        let line_number_width = last_layout.line_number_width;\n        let range = self.range_from_utf16(&range_utf16);\n\n        let mut start_origin = None;\n        let mut end_origin = None;\n        let line_number_origin = point(line_number_width, px(0.));\n        let mut y_offset = last_layout.visible_top;\n\n        for (vi, line) in last_layout.lines.iter().enumerate() {\n            if start_origin.is_some() && end_origin.is_some() {\n                break;\n            }\n\n            let index_offset = last_layout.visible_line_byte_offsets[vi];\n\n            if start_origin.is_none() {\n                if let Some(p) =\n                    line.position_for_index(range.start.saturating_sub(index_offset), last_layout)\n                {\n                    start_origin = Some(p + point(px(0.), y_offset));\n                }\n            }\n\n            if end_origin.is_none() {\n                if let Some(p) =\n                    line.position_for_index(range.end.saturating_sub(index_offset), last_layout)\n                {\n                    end_origin = Some(p + point(px(0.), y_offset));\n                }\n            }\n\n            y_offset += line.size(line_height).height;\n        }\n\n        let start_origin = start_origin.unwrap_or_default();\n        let mut end_origin = end_origin.unwrap_or_default();\n        // Ensure at same line.\n        end_origin.y = start_origin.y;\n\n        Some(Bounds::from_corners(\n            bounds.origin + line_number_origin + start_origin,\n            // + line_height for show IME panel under the cursor line.\n            bounds.origin + line_number_origin + point(end_origin.x, end_origin.y + line_height),\n        ))\n    }\n\n    fn character_index_for_point(\n        &mut self,\n        point: gpui::Point<Pixels>,\n        _window: &mut Window,\n        _cx: &mut Context<Self>,\n    ) -> Option<usize> {\n        let last_layout = self.last_layout.as_ref()?;\n        let line_point = self.last_bounds?.localize(&point)?;\n\n        for (vi, line) in last_layout.lines.iter().enumerate() {\n            let offset = last_layout.visible_line_byte_offsets[vi];\n            if let Some(utf8_index) = line.index_for_position(line_point, last_layout) {\n                return Some(self.offset_to_utf16(offset + utf8_index));\n            }\n        }\n\n        None\n    }\n}\n\nimpl Focusable for InputState {\n    fn focus_handle(&self, _cx: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for InputState {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        if self._pending_update {\n            let bg = self\n                .mode\n                .update_highlighter(&(0..0), &self.text, \"\", false, cx);\n            if let Some(bg) = bg {\n                Self::dispatch_background_parse(bg, window, cx);\n            }\n\n            self.update_fold_candidates();\n            self.lsp.update(&self.text, window, cx);\n            self._pending_update = false;\n        }\n\n        div()\n            .id(\"input-state\")\n            .flex_1()\n            .when(self.mode.is_multi_line(), |this| this.h_full())\n            .flex_grow()\n            .overflow_x_hidden()\n            .child(TextElement::new(cx.entity().clone()).placeholder(self.placeholder.clone()))\n            .children(self.diagnostic_popover.clone())\n            .children(self.context_menu.as_ref().map(|menu| menu.render()))\n            .children(self.hover_popover.clone())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::theme::Theme;\n    use gpui::{TestAppContext, VisualTestContext};\n\n    struct InputView {\n        input: Entity<InputState>,\n        window_handle: gpui::WindowHandle<Root>,\n    }\n\n    /// Helper to create an InputState in a window for testing\n    impl InputView {\n        pub fn new(cx: &mut TestAppContext) -> Self {\n            let mut input: Option<Entity<InputState>> = None;\n\n            let window = cx.update(|cx| {\n                cx.open_window(Default::default(), |window, cx| {\n                    // Set up the theme first\n                    cx.set_global(Theme::default());\n                    // Initialize input keybindings\n                    super::super::init(cx);\n\n                    input = Some(cx.new(|cx| InputState::new(window, cx).code_editor(\"sql\")));\n\n                    cx.new(|cx| crate::Root::new(input.clone().unwrap(), window, cx))\n                })\n                .unwrap()\n            });\n\n            Self {\n                input: input.clone().unwrap(),\n                window_handle: window,\n            }\n        }\n    }\n\n    #[gpui::test]\n    fn test_highlighting_preserved_after_fold(cx: &mut TestAppContext) {\n        use crate::highlighter::HighlightTheme;\n        use crate::input::display_map::FoldRange;\n\n        let input_view = InputView::new(cx);\n        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);\n        let input = input_view.input;\n\n        // SQL text: fold the SELECT..WHERE block, verify comments keep highlighting.\n        // Lines 0-9: SELECT block (fold range 0..9 hides lines 1-8)\n        // Line 10+: comments that must keep highlighting\n        let text = \"\\\nSELECT *\nFROM users\nWHERE id = 1\nAND name = 'test'\nAND active = true\nAND role = 'admin'\nAND age > 18\nAND status = 'ok'\nAND country = 'US'\nORDER BY id\n\n-- Comment 1\n-- Comment 2\n-- Comment 3\";\n\n        cx.update(|window, cx| {\n            input.update(cx, |state, cx| {\n                state.set_value(text, window, cx);\n            });\n        });\n        cx.run_until_parked();\n\n        // Grab styles for \"-- Comment 1\" (line 11) before folding\n        let theme = HighlightTheme::default_dark();\n        let comment_line = 11;\n        let comment_start = cx.update(|_, cx| {\n            input.read_with(cx, |state, _| state.text.line_start_offset(comment_line))\n        });\n        let styles_before: Vec<(Range<usize>, gpui::HighlightStyle)> = cx.update(|_, cx| {\n            input.read_with(cx, |state, _| {\n                let mode = &state.mode;\n                if let crate::input::mode::InputMode::CodeEditor { highlighter, .. } = mode {\n                    let h = highlighter.borrow();\n                    if let Some(h) = h.as_ref() {\n                        let line_end = state.text.line_end_offset(comment_line);\n                        return h.styles(&(comment_start..line_end), &theme);\n                    }\n                }\n                vec![]\n            })\n        });\n\n        // Fold at line 0 with range 0..9 (hides lines 1-8)\n        cx.update(|_, cx| {\n            input.update(cx, |state, _cx| {\n                state\n                    .display_map\n                    .set_fold_candidates(vec![FoldRange::new(0, 9)]);\n                state.display_map.set_folded(0, true);\n            });\n        });\n        cx.run_until_parked();\n\n        // Verify fold is active and lines 1-8 are hidden\n        cx.update(|_, cx| {\n            input.read_with(cx, |state, _| {\n                assert!(state.display_map.is_folded_at(0));\n                for line in 1..=8 {\n                    assert!(\n                        state.display_map.is_buffer_line_hidden(line),\n                        \"Line {} should be hidden\",\n                        line\n                    );\n                }\n                assert!(\n                    !state.display_map.is_buffer_line_hidden(9),\n                    \"Line 9 (ORDER BY) should be visible\"\n                );\n            });\n        });\n\n        // Get styles for the same comment line after folding\n        let styles_after: Vec<(Range<usize>, gpui::HighlightStyle)> = cx.update(|_, cx| {\n            input.read_with(cx, |state, _| {\n                let mode = &state.mode;\n                if let crate::input::mode::InputMode::CodeEditor { highlighter, .. } = mode {\n                    let h = highlighter.borrow();\n                    if let Some(h) = h.as_ref() {\n                        let line_end = state.text.line_end_offset(comment_line);\n                        return h.styles(&(comment_start..line_end), &theme);\n                    }\n                }\n                vec![]\n            })\n        });\n\n        let colored_before: Vec<_> = styles_before\n            .iter()\n            .filter(|(_, s)| s.color.is_some())\n            .cloned()\n            .collect();\n        let colored_after: Vec<_> = styles_after\n            .iter()\n            .filter(|(_, s)| s.color.is_some())\n            .cloned()\n            .collect();\n\n        assert_eq!(\n            colored_before, colored_after,\n            \"Comment highlighting must be identical before and after folding.\\n\\\n             Before: {:?}\\nAfter: {:?}\",\n            colored_before, colored_after\n        );\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/inspector.rs",
    "content": "use std::{cell::OnceCell, collections::HashMap, fmt::Write as _, rc::Rc, sync::OnceLock};\n\nuse anyhow::Result;\nuse gpui::{\n    actions, div, inspector_reflection::FunctionReflection, prelude::FluentBuilder, px, AnyElement,\n    App, AppContext, Context, DivInspectorState, Entity, Inspector, InspectorElementId,\n    InteractiveElement as _, IntoElement, KeyBinding, ParentElement as _, Refineable as _, Render,\n    SharedString, StyleRefinement, Styled, Subscription, Task, Window,\n};\nuse lsp_types::{\n    CompletionItem, CompletionItemKind, CompletionResponse, CompletionTextEdit, Diagnostic,\n    DiagnosticSeverity, Position, TextEdit,\n};\nuse ropey::Rope;\n\nuse crate::{\n    alert::Alert,\n    button::{Button, ButtonVariants},\n    clipboard::Clipboard,\n    description_list::DescriptionList,\n    h_flex,\n    input::{CompletionProvider, Input, InputEvent, InputState, RopeExt, TabSize},\n    link::Link,\n    v_flex, ActiveTheme, IconName, Selectable, Sizable, TITLE_BAR_HEIGHT,\n};\n\nactions!(inspector, [ToggleInspector]);\n\n/// Initialize the inspector and register the action to toggle it.\npub(crate) fn init(cx: &mut App) {\n    cx.bind_keys(vec![\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-alt-i\", ToggleInspector, None),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-shift-i\", ToggleInspector, None),\n    ]);\n\n    cx.on_action(|_: &ToggleInspector, cx| {\n        let Some(active_window) = cx.active_window() else {\n            return;\n        };\n\n        cx.defer(move |cx| {\n            _ = active_window.update(cx, |_, window, cx| {\n                window.toggle_inspector(cx);\n            });\n        });\n    });\n\n    let inspector_el = OnceCell::new();\n    cx.register_inspector_element(move |id, state: &DivInspectorState, window, cx| {\n        let el = inspector_el.get_or_init(|| cx.new(|cx| DivInspector::new(window, cx)));\n        el.update(cx, |this, cx| {\n            this.update_inspected_element(id, state.clone(), window, cx);\n            this.render(window, cx).into_any_element()\n        })\n    });\n\n    cx.set_inspector_renderer(Box::new(render_inspector));\n}\n\nstruct EditorState {\n    /// The input state for the editor.\n    state: Entity<InputState>,\n    /// Error to display from parsing the input, or if serialization errors somehow occur.\n    error: Option<SharedString>,\n    /// Whether the editor is currently being edited.\n    editing: bool,\n}\n\npub struct DivInspector {\n    inspector_id: Option<InspectorElementId>,\n    inspector_state: Option<DivInspectorState>,\n    rust_state: EditorState,\n    json_state: EditorState,\n    /// Initial style before any edits\n    initial_style: StyleRefinement,\n    /// Part of the initial style that could not be converted to Rust code\n    unconvertible_style: StyleRefinement,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl DivInspector {\n    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let lsp_provider = Rc::new(LspProvider {});\n\n        let json_input_state = cx.new(|cx| {\n            InputState::new(window, cx)\n                .code_editor(\"json\")\n                .line_number(false)\n        });\n\n        let rust_input_state = cx.new(|cx| {\n            let mut editor = InputState::new(window, cx)\n                .code_editor(\"rust\")\n                .line_number(false)\n                .tab_size(TabSize {\n                    tab_size: 4,\n                    hard_tabs: false,\n                });\n\n            editor.lsp.completion_provider = Some(lsp_provider.clone());\n            editor\n        });\n\n        let _subscriptions = vec![\n            cx.subscribe_in(\n                &json_input_state,\n                window,\n                |this: &mut DivInspector, state, event: &InputEvent, window, cx| match event {\n                    InputEvent::Change => {\n                        let new_style = state.read(cx).value();\n                        this.edit_json(new_style.as_str(), window, cx);\n                    }\n                    _ => {}\n                },\n            ),\n            cx.subscribe_in(\n                &rust_input_state,\n                window,\n                |this: &mut DivInspector, state, event: &InputEvent, window, cx| match event {\n                    InputEvent::Change => {\n                        let new_style = state.read(cx).value();\n                        this.edit_rust(new_style.as_str(), window, cx);\n                    }\n                    _ => {}\n                },\n            ),\n        ];\n\n        let rust_state = EditorState {\n            state: rust_input_state,\n            error: None,\n            editing: false,\n        };\n\n        let json_state = EditorState {\n            state: json_input_state,\n            error: None,\n            editing: false,\n        };\n\n        Self {\n            inspector_id: None,\n            inspector_state: None,\n            rust_state,\n            json_state,\n            initial_style: Default::default(),\n            unconvertible_style: Default::default(),\n            _subscriptions,\n        }\n    }\n\n    pub fn update_inspected_element(\n        &mut self,\n        inspector_id: InspectorElementId,\n        state: DivInspectorState,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        // Skip updating if the inspector ID hasn't changed\n        if self.inspector_id.as_ref() == Some(&inspector_id) {\n            return;\n        }\n\n        let initial_style = state.base_style.as_ref();\n        self.initial_style = initial_style.clone();\n        self.json_state.editing = false;\n        self.update_json_from_style(initial_style, window, cx);\n        self.rust_state.editing = false;\n        let rust_style = self.update_rust_from_style(initial_style, window, cx);\n        self.unconvertible_style = initial_style.subtract(&rust_style);\n        self.inspector_id = Some(inspector_id);\n        self.inspector_state = Some(state);\n        cx.notify();\n    }\n\n    fn edit_json(&mut self, code: &str, window: &mut Window, cx: &mut Context<Self>) {\n        if !self.json_state.editing {\n            self.json_state.editing = true;\n            return;\n        }\n\n        match serde_json::from_str::<StyleRefinement>(code) {\n            Ok(new_style) => {\n                self.json_state.error = None;\n                self.rust_state.error = None;\n                self.rust_state.editing = false;\n                let rust_style = self.update_rust_from_style(&new_style, window, cx);\n                self.unconvertible_style = new_style.subtract(&rust_style);\n                self.update_element_style(new_style, window, cx);\n            }\n            Err(e) => {\n                self.json_state.error = Some(e.to_string().trim_end().to_string().into());\n                window.refresh();\n            }\n        }\n    }\n\n    fn edit_rust(&mut self, code: &str, window: &mut Window, cx: &mut Context<Self>) {\n        if !self.rust_state.editing {\n            self.rust_state.editing = true;\n            return;\n        }\n\n        let (new_style, diagnostics) = rust_to_style(self.unconvertible_style.clone(), code);\n        self.rust_state.state.update(cx, |state, cx| {\n            if let Some(set) = state.diagnostics_mut() {\n                set.clear();\n                set.extend(diagnostics);\n            }\n            cx.notify();\n        });\n        self.json_state.error = None;\n        self.json_state.editing = false;\n        self.update_json_from_style(&new_style, window, cx);\n        self.update_element_style(new_style, window, cx);\n    }\n\n    fn update_element_style(\n        &self,\n        style: StyleRefinement,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        window.with_inspector_state::<DivInspectorState, _>(\n            self.inspector_id.as_ref(),\n            cx,\n            |state, _window| {\n                if let Some(state) = state {\n                    *state.base_style = style;\n                }\n            },\n        );\n        window.refresh();\n    }\n\n    fn reset_style(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        self.rust_state.editing = false;\n        let rust_style = self.update_rust_from_style(&self.initial_style, window, cx);\n        self.unconvertible_style = self.initial_style.subtract(&rust_style);\n        self.json_state.editing = false;\n        self.update_json_from_style(&self.initial_style, window, cx);\n        if let Some(state) = self.inspector_state.as_mut() {\n            *state.base_style = self.initial_style.clone();\n        }\n    }\n\n    fn update_json_from_style(\n        &self,\n        style: &StyleRefinement,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.json_state.state.update(cx, |state, cx| {\n            state.set_value(style_to_json(style), window, cx);\n        });\n    }\n\n    fn update_rust_from_style(\n        &self,\n        style: &StyleRefinement,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> StyleRefinement {\n        self.rust_state.state.update(cx, |state, cx| {\n            let (rust_code, rust_style) = style_to_rust(style);\n            state.set_value(rust_code, window, cx);\n            rust_style\n        })\n    }\n}\n\nfn style_to_json(style: &StyleRefinement) -> String {\n    serde_json::to_string_pretty(style).unwrap_or_else(|e| format!(\"{{ \\\"error\\\": \\\"{}\\\" }}\", e))\n}\n\nstruct StyleMethods {\n    table: Vec<(Box<StyleRefinement>, FunctionReflection<StyleRefinement>)>,\n    map: HashMap<&'static str, FunctionReflection<StyleRefinement>>,\n}\n\nimpl StyleMethods {\n    fn get() -> &'static Self {\n        static STYLE_METHODS: OnceLock<StyleMethods> = OnceLock::new();\n        STYLE_METHODS.get_or_init(|| {\n            let table: Vec<_> = [\n                crate::styled_ext_reflection::methods::<StyleRefinement>(),\n                gpui::styled_reflection::methods::<StyleRefinement>(),\n            ]\n            .into_iter()\n            .flatten()\n            .map(|method| (Box::new(method.invoke(StyleRefinement::default())), method))\n            .collect();\n            let map = table\n                .iter()\n                .map(|(_, method)| (method.name, method.clone()))\n                .collect();\n\n            Self { table, map }\n        })\n    }\n}\n\nfn style_to_rust(input_style: &StyleRefinement) -> (String, StyleRefinement) {\n    let methods: Vec<_> = StyleMethods::get()\n        .table\n        .iter()\n        .filter_map(|(style, method)| {\n            if input_style.is_superset_of(style) {\n                Some(method)\n            } else {\n                None\n            }\n        })\n        .collect();\n    let mut code = \"fn build() -> Div {\\n    div()\\n\".to_string();\n    let mut style = StyleRefinement::default();\n    for method in methods {\n        let before_invoke = style.clone();\n        style = method.invoke(style);\n        if style != before_invoke {\n            _ = write!(code, \"        .{}()\\n\", method.name);\n        }\n    }\n    code.push_str(\"}\");\n    (code, style)\n}\n\nfn rust_to_style(mut style: StyleRefinement, source: &str) -> (StyleRefinement, Vec<Diagnostic>) {\n    let rope = Rope::from(source);\n    let Some(begin) = source.find(\"div()\").map(|i| i + \"div()\".len()) else {\n        let start_pos = Position::new(0, 0);\n        let end_pos = rope.offset_to_position(rope.len());\n\n        return (\n            style,\n            vec![Diagnostic {\n                range: lsp_types::Range::new(start_pos, end_pos),\n                severity: Some(DiagnosticSeverity::ERROR),\n                message: \"expected `div()`\".into(),\n                ..Default::default()\n            }],\n        );\n    };\n\n    let mut methods = vec![];\n    let mut offset = 0;\n    let mut method_offset = 0;\n    let mut method = String::new();\n    for line in rope.iter_lines() {\n        if line.to_string().trim().starts_with(\"//\") {\n            offset += line.len() + 1;\n            continue;\n        }\n\n        for c in line.chars() {\n            offset += c.len_utf8();\n            if offset < begin {\n                continue;\n            }\n\n            if c.is_ascii_alphanumeric() || c == '_' {\n                method.push(c);\n                method_offset = offset;\n            } else {\n                if !method.is_empty() {\n                    methods.push((method_offset, method.clone()));\n                }\n                method.clear();\n            }\n        }\n\n        // +1 \\n\n        offset += 1;\n    }\n\n    let mut diagnostics = vec![];\n    let style_methods = StyleMethods::get();\n\n    for (offset, method) in methods {\n        match style_methods.map.get(method.as_str()) {\n            Some(method_reflection) => style = method_reflection.invoke(style),\n            None => {\n                let message = format!(\"unknown method `{}`\", method);\n                let start = rope.offset_to_position(offset.saturating_sub(method.len()));\n                let end = rope.offset_to_position(offset);\n                let diagnostic = lsp_types::Diagnostic {\n                    range: lsp_types::Range::new(start, end),\n                    severity: Some(DiagnosticSeverity::ERROR),\n                    message,\n                    ..Default::default()\n                };\n\n                diagnostics.push(diagnostic);\n            }\n        }\n    }\n\n    (style, diagnostics)\n}\n\nimpl Render for DivInspector {\n    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex().size_full().gap_y_4().text_sm().when_some(\n            self.inspector_state.as_ref(),\n            |this, state| {\n                this.child(\n                    DescriptionList::new()\n                        .columns(1)\n                        .label_width(px(110.))\n                        .bordered(false)\n                        .item(\"Origin\", format!(\"{}\", state.bounds.origin), 1)\n                        .item(\"Size\", format!(\"{}\", state.bounds.size), 1)\n                        .item(\"Content Size\", format!(\"{}\", state.content_size), 1),\n                )\n                .child(\n                    v_flex()\n                        .flex_1()\n                        .h_2_5()\n                        .gap_y_3()\n                        .child(\n                            h_flex()\n                                .justify_between()\n                                .gap_x_2()\n                                .child(\"Rust Styles\")\n                                .child(Button::new(\"rust-reset\").label(\"Reset\").small().on_click(\n                                    cx.listener(|this, _, window, cx| {\n                                        this.reset_style(window, cx);\n                                    }),\n                                )),\n                        )\n                        .child(\n                            v_flex()\n                                .flex_1()\n                                .gap_y_1()\n                                .font_family(cx.theme().mono_font_family.clone())\n                                .text_size(cx.theme().mono_font_size)\n                                .child(Input::new(&self.rust_state.state).h_full())\n                                .when_some(self.rust_state.error.clone(), |this, err| {\n                                    this.child(Alert::error(\"rust-error\", err).text_xs())\n                                }),\n                        ),\n                )\n                .child(\n                    v_flex()\n                        .flex_1()\n                        .gap_y_3()\n                        .h_2_5()\n                        .flex_shrink_0()\n                        .child(\n                            h_flex()\n                                .gap_x_2()\n                                .child(div().flex_1().child(\"JSON Styles\"))\n                                .child(Button::new(\"json-reset\").label(\"Reset\").small().on_click(\n                                    cx.listener(|this, _, window, cx| {\n                                        this.reset_style(window, cx);\n                                    }),\n                                )),\n                        )\n                        .child(\n                            v_flex()\n                                .flex_1()\n                                .gap_y_1()\n                                .font_family(cx.theme().mono_font_family.clone())\n                                .text_size(cx.theme().mono_font_size)\n                                .child(Input::new(&self.json_state.state).h_full())\n                                .when_some(self.json_state.error.clone(), |this, err| {\n                                    this.child(Alert::error(\"json-error\", err).text_xs())\n                                }),\n                        ),\n                )\n            },\n        )\n    }\n}\n\nfn render_inspector(\n    inspector: &mut Inspector,\n    window: &mut Window,\n    cx: &mut Context<Inspector>,\n) -> AnyElement {\n    let inspector_element_id = inspector.active_element_id();\n    let source_location =\n        inspector_element_id.map(|id| SharedString::new(format!(\"{}\", id.path.source_location)));\n    let element_global_id = inspector_element_id.map(|id| format!(\"{}\", id.path.global_id));\n\n    v_flex()\n        .id(\"inspector\")\n        .font_family(cx.theme().font_family.clone())\n        .size_full()\n        .bg(cx.theme().background)\n        .border_l_1()\n        .border_color(cx.theme().border)\n        .text_color(cx.theme().foreground)\n        .child(\n            h_flex()\n                .w_full()\n                .justify_between()\n                .gap_2()\n                .h(TITLE_BAR_HEIGHT)\n                .line_height(TITLE_BAR_HEIGHT)\n                .overflow_x_hidden()\n                .px_2()\n                .border_b_1()\n                .border_color(cx.theme().title_bar_border)\n                .bg(cx.theme().title_bar)\n                .child(\n                    h_flex()\n                        .gap_2()\n                        .text_sm()\n                        .child(\n                            Button::new(\"inspect\")\n                                .icon(IconName::Inspector)\n                                .selected(inspector.is_picking())\n                                .small()\n                                .ghost()\n                                .on_click(cx.listener(|this, _, window, _| {\n                                    this.start_picking();\n                                    window.refresh();\n                                })),\n                        )\n                        .child(\"Inspector\"),\n                )\n                .child(\n                    Button::new(\"close\")\n                        .icon(IconName::Close)\n                        .small()\n                        .ghost()\n                        .on_click(|_, window, cx| {\n                            window.dispatch_action(Box::new(ToggleInspector), cx);\n                        }),\n                ),\n        )\n        .child(\n            v_flex()\n                .flex_1()\n                .p_3()\n                .gap_y_3()\n                .text_sm()\n                .when_some(source_location, |this, source_location| {\n                    this.child(\n                        h_flex()\n                            .gap_x_2()\n                            .text_sm()\n                            .child(\n                                Link::new(\"source-location\")\n                                    .href(format!(\"file://{}\", source_location))\n                                    .child(source_location.clone())\n                                    .flex_1()\n                                    .overflow_x_hidden(),\n                            )\n                            .child(Clipboard::new(\"copy-source-location\").value(source_location)),\n                    )\n                })\n                .children(element_global_id)\n                .children(inspector.render_inspector_states(window, cx)),\n        )\n        .into_any_element()\n}\n\nstruct LspProvider {}\n\nimpl CompletionProvider for LspProvider {\n    fn completions(\n        &self,\n        rope: &ropey::Rope,\n        offset: usize,\n        _: lsp_types::CompletionContext,\n        _: &mut Window,\n        cx: &mut Context<InputState>,\n    ) -> Task<Result<CompletionResponse>> {\n        let mut left_offset = 0;\n        while left_offset < 100 {\n            match rope.char_at(offset.saturating_sub(left_offset)) {\n                Some('.') => {\n                    break;\n                }\n                None => break,\n                _ => {}\n            }\n            left_offset += 1;\n        }\n        let start = offset.saturating_sub(left_offset);\n        let trigger_character = rope.slice(start..offset).to_string();\n        if !trigger_character.starts_with('.') {\n            return Task::ready(Ok(CompletionResponse::Array(vec![])));\n        }\n\n        let start_pos = rope.offset_to_position(start);\n        let end_pos = rope.offset_to_position(offset);\n\n        cx.background_spawn(async move {\n            let styles = StyleMethods::get()\n                .map\n                .iter()\n                .filter_map(|(name, method)| {\n                    let prefix = &trigger_character[1..];\n                    if name.starts_with(&prefix) {\n                        Some(CompletionItem {\n                            label: name.to_string(),\n                            filter_text: Some(prefix.to_string()),\n                            kind: Some(CompletionItemKind::METHOD),\n                            detail: Some(\"()\".to_string()),\n                            documentation: method\n                                .documentation\n                                .as_ref()\n                                .map(|doc| lsp_types::Documentation::String(doc.to_string())),\n                            text_edit: Some(CompletionTextEdit::Edit(TextEdit {\n                                range: lsp_types::Range {\n                                    start: start_pos,\n                                    end: end_pos,\n                                },\n                                new_text: format!(\".{}()\", name),\n                            })),\n                            ..Default::default()\n                        })\n                    } else {\n                        None\n                    }\n                })\n                .collect::<Vec<_>>();\n\n            Ok(CompletionResponse::Array(styles))\n        })\n    }\n\n    fn is_completion_trigger(&self, _: usize, _: &str, _: &mut Context<InputState>) -> bool {\n        true\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use gpui::{rems, AbsoluteLength, DefiniteLength, Length};\n    use indoc::indoc;\n    use lsp_types::Position;\n\n    #[test]\n    fn test_rust_to_style() {\n        let (style, diagnostics) = super::rust_to_style(\n            Default::default(),\n            indoc! {r#\"\n            fn build() -> Div {\n                div()\n                    .p_1()\n                    // This is a comment\n                    .mx_2()\n            }\n            \"#},\n        );\n        assert_eq!(diagnostics, vec![]);\n        assert_eq!(\n            style.padding.left,\n            Some(DefiniteLength::Absolute(AbsoluteLength::Rems(rems(0.25))))\n        );\n        assert_eq!(\n            style.margin.left,\n            Some(Length::Definite(DefiniteLength::Absolute(\n                AbsoluteLength::Rems(rems(0.5))\n            )))\n        );\n\n        let (_, diagnostics) = super::rust_to_style(\n            Default::default(),\n            indoc! {r#\"\n            fn build() -> Div {\n                div()\n                    .p_1()\n                    // This is a comment\n                    .unknown_method\n                    .bad_method()\n            }\n            \"#},\n        );\n\n        assert_eq!(diagnostics.len(), 2);\n        assert_eq!(diagnostics[0].message, \"unknown method `unknown_method`\");\n        assert_eq!(diagnostics[0].range.start, Position::new(4, 9));\n        assert_eq!(diagnostics[0].range.end, Position::new(4, 23));\n        assert_eq!(diagnostics[1].message, \"unknown method `bad_method`\");\n        assert_eq!(diagnostics[1].range.start, Position::new(5, 9));\n        assert_eq!(diagnostics[1].range.end, Position::new(5, 19));\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/kbd.rs",
    "content": "use gpui::{\n    Action, AsKeystroke, FocusHandle, Half, IntoElement, KeyContext, Keystroke, ParentElement as _,\n    RenderOnce, StyleRefinement, Styled, Window, div, prelude::FluentBuilder as _, relative,\n};\n\nuse crate::{ActiveTheme, StyledExt};\n\n/// A tag for displaying keyboard keybindings.\n#[derive(IntoElement, Clone, Debug)]\npub struct Kbd {\n    style: StyleRefinement,\n    stroke: Keystroke,\n    appearance: bool,\n    outline: bool,\n}\n\nimpl From<Keystroke> for Kbd {\n    fn from(stroke: Keystroke) -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            stroke,\n            appearance: true,\n            outline: false,\n        }\n    }\n}\n\nimpl Kbd {\n    /// Create a new Kbd element with the given [`Keystroke`].\n    pub fn new(stroke: Keystroke) -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            stroke,\n            appearance: true,\n            outline: false,\n        }\n    }\n\n    /// Set the appearance of the keybinding, default is `true`.\n    pub fn appearance(mut self, appearance: bool) -> Self {\n        self.appearance = appearance;\n        self\n    }\n\n    /// Use outline style for the keybinding, default is `false`.\n    pub fn outline(mut self) -> Self {\n        self.outline = true;\n        self\n    }\n\n    /// Return the first keybinding for the given action and context.\n    pub fn binding_for_action(\n        action: &dyn Action,\n        context: Option<&str>,\n        window: &Window,\n    ) -> Option<Self> {\n        let key_context = context.and_then(|context| KeyContext::parse(context).ok());\n        let binding = match key_context {\n            Some(context) => {\n                window.highest_precedence_binding_for_action_in_context(action, context)\n            }\n            None => window.highest_precedence_binding_for_action(action),\n        }?;\n\n        if let Some(key) = binding.keystrokes().first() {\n            Some(Self::new(key.as_keystroke().clone()))\n        } else {\n            None\n        }\n    }\n\n    /// Return the first keybinding for the given action and focus handle.\n    pub fn binding_for_action_in(\n        action: &dyn Action,\n        focus_handle: &FocusHandle,\n        window: &Window,\n    ) -> Option<Self> {\n        let binding = window.highest_precedence_binding_for_action_in(action, focus_handle)?;\n        if let Some(key) = binding.keystrokes().first() {\n            Some(Self::new(key.as_keystroke().clone()))\n        } else {\n            None\n        }\n    }\n\n    /// Return the Platform specific keybinding string by KeyStroke\n    ///\n    /// macOS: https://support.apple.com/en-us/HT201236\n    /// Windows: https://support.microsoft.com/en-us/windows/keyboard-shortcuts-in-windows-dcc61a57-8ff0-cffe-9796-cb9706c75eec\n    pub fn format(key: &Keystroke) -> String {\n        #[cfg(target_os = \"macos\")]\n        const DIVIDER: &str = \"\";\n        #[cfg(not(target_os = \"macos\"))]\n        const DIVIDER: &str = \"+\";\n\n        let mut parts = vec![];\n\n        // The key map order in macOS is: ⌃⌥⇧⌘\n        // And in Windows is: Ctrl+Alt+Shift+Win\n\n        if key.modifiers.control {\n            #[cfg(target_os = \"macos\")]\n            parts.push(\"⌃\");\n\n            #[cfg(not(target_os = \"macos\"))]\n            parts.push(\"Ctrl\");\n        }\n\n        if key.modifiers.alt {\n            #[cfg(target_os = \"macos\")]\n            parts.push(\"⌥\");\n\n            #[cfg(not(target_os = \"macos\"))]\n            parts.push(\"Alt\");\n        }\n\n        if key.modifiers.shift {\n            #[cfg(target_os = \"macos\")]\n            parts.push(\"⇧\");\n\n            #[cfg(not(target_os = \"macos\"))]\n            parts.push(\"Shift\");\n        }\n\n        if key.modifiers.platform {\n            #[cfg(target_os = \"macos\")]\n            parts.push(\"⌘\");\n\n            #[cfg(not(target_os = \"macos\"))]\n            parts.push(\"Win\");\n        }\n\n        let mut keys = String::new();\n        let key_str = key.key.as_str();\n        match key_str {\n            #[cfg(target_os = \"macos\")]\n            \"ctrl\" => keys.push('⌃'),\n            #[cfg(not(target_os = \"macos\"))]\n            \"ctrl\" => keys.push_str(\"Ctrl\"),\n            #[cfg(target_os = \"macos\")]\n            \"alt\" => keys.push('⌥'),\n            #[cfg(not(target_os = \"macos\"))]\n            \"alt\" => keys.push_str(\"Alt\"),\n            #[cfg(target_os = \"macos\")]\n            \"shift\" => keys.push('⇧'),\n            #[cfg(not(target_os = \"macos\"))]\n            \"shift\" => keys.push_str(\"Shift\"),\n            #[cfg(target_os = \"macos\")]\n            \"cmd\" => keys.push('⌘'),\n            #[cfg(not(target_os = \"macos\"))]\n            \"cmd\" => keys.push_str(\"Win\"),\n            #[cfg(target_os = \"macos\")]\n            \"space\" => keys.push_str(\"Space\"),\n            #[cfg(target_os = \"macos\")]\n            \"backspace\" => keys.push('⌫'),\n            #[cfg(not(target_os = \"macos\"))]\n            \"backspace\" => keys.push_str(\"Backspace\"),\n            #[cfg(target_os = \"macos\")]\n            \"delete\" => keys.push('⌫'),\n            #[cfg(not(target_os = \"macos\"))]\n            \"delete\" => keys.push_str(\"Delete\"),\n            #[cfg(target_os = \"macos\")]\n            \"escape\" => keys.push('⎋'),\n            #[cfg(not(target_os = \"macos\"))]\n            \"escape\" => keys.push_str(\"Esc\"),\n            #[cfg(target_os = \"macos\")]\n            \"enter\" => keys.push('⏎'),\n            #[cfg(not(target_os = \"macos\"))]\n            \"enter\" => keys.push_str(\"Enter\"),\n            \"pagedown\" => keys.push_str(\"Page Down\"),\n            \"pageup\" => keys.push_str(\"Page Up\"),\n            #[cfg(target_os = \"macos\")]\n            \"left\" => keys.push('←'),\n            #[cfg(not(target_os = \"macos\"))]\n            \"left\" => keys.push_str(\"Left\"),\n            #[cfg(target_os = \"macos\")]\n            \"right\" => keys.push('→'),\n            #[cfg(not(target_os = \"macos\"))]\n            \"right\" => keys.push_str(\"Right\"),\n            #[cfg(target_os = \"macos\")]\n            \"up\" => keys.push('↑'),\n            #[cfg(not(target_os = \"macos\"))]\n            \"up\" => keys.push_str(\"Up\"),\n            #[cfg(target_os = \"macos\")]\n            \"down\" => keys.push('↓'),\n            #[cfg(not(target_os = \"macos\"))]\n            \"down\" => keys.push_str(\"Down\"),\n            _ => {\n                if key_str.len() == 1 {\n                    keys.push_str(&key_str.to_uppercase());\n                } else {\n                    let mut chars = key_str.chars();\n                    if let Some(first_char) = chars.next() {\n                        keys.push_str(&format!(\n                            \"{}{}\",\n                            first_char.to_uppercase(),\n                            chars.collect::<String>()\n                        ));\n                    } else {\n                        keys.push_str(&key_str);\n                    }\n                }\n            }\n        }\n\n        parts.push(&keys);\n        parts.join(DIVIDER)\n    }\n}\n\nimpl Styled for Kbd {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Kbd {\n    fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement {\n        if !self.appearance {\n            return Self::format(&self.stroke).into_any_element();\n        }\n\n        div()\n            .text_color(cx.theme().muted_foreground)\n            .bg(cx.theme().muted)\n            .when(self.outline, |this| {\n                this.border_1()\n                    .border_color(cx.theme().border)\n                    .bg(cx.theme().background)\n            })\n            .py_0p5()\n            .px_1()\n            .min_w_5()\n            .text_center()\n            .rounded(cx.theme().radius.half())\n            .line_height(relative(1.))\n            .text_xs()\n            .whitespace_normal()\n            .flex_shrink_0()\n            .refine_style(&self.style)\n            .child(Self::format(&self.stroke))\n            .into_any_element()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    #[test]\n    fn test_format() {\n        use super::Kbd;\n        use gpui::Keystroke;\n\n        if cfg!(target_os = \"macos\") {\n            assert_eq!(Kbd::format(&Keystroke::parse(\"cmd-a\").unwrap()), \"⌘A\");\n            assert_eq!(Kbd::format(&Keystroke::parse(\"cmd--\").unwrap()), \"⌘-\");\n            assert_eq!(Kbd::format(&Keystroke::parse(\"cmd-+\").unwrap()), \"⌘+\");\n            assert_eq!(Kbd::format(&Keystroke::parse(\"cmd-enter\").unwrap()), \"⌘⏎\");\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"secondary-f12\").unwrap()),\n                \"⌘F12\"\n            );\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"shift-pagedown\").unwrap()),\n                \"⇧Page Down\"\n            );\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"shift-pageup\").unwrap()),\n                \"⇧Page Up\"\n            );\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"shift-space\").unwrap()),\n                \"⇧Space\"\n            );\n            assert_eq!(Kbd::format(&Keystroke::parse(\"cmd-ctrl-a\").unwrap()), \"⌃⌘A\");\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"cmd-alt-backspace\").unwrap()),\n                \"⌥⌘⌫\"\n            );\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"shift-delete\").unwrap()),\n                \"⇧⌫\"\n            );\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"cmd-ctrl-shift-a\").unwrap()),\n                \"⌃⇧⌘A\"\n            );\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"cmd-ctrl-shift-alt-a\").unwrap()),\n                \"⌃⌥⇧⌘A\"\n            );\n        } else {\n            assert_eq!(Kbd::format(&Keystroke::parse(\"a\").unwrap()), \"A\");\n            assert_eq!(Kbd::format(&Keystroke::parse(\"ctrl-a\").unwrap()), \"Ctrl+A\");\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"shift-space\").unwrap()),\n                \"Shift+Space\"\n            );\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"ctrl-alt-a\").unwrap()),\n                \"Ctrl+Alt+A\"\n            );\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"ctrl-alt-shift-a\").unwrap()),\n                \"Ctrl+Alt+Shift+A\"\n            );\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"ctrl-alt-shift-win-a\").unwrap()),\n                \"Ctrl+Alt+Shift+Win+A\"\n            );\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"ctrl-shift-backspace\").unwrap()),\n                \"Ctrl+Shift+Backspace\"\n            );\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"alt-delete\").unwrap()),\n                \"Alt+Delete\"\n            );\n            assert_eq!(\n                Kbd::format(&Keystroke::parse(\"alt-tab\").unwrap()),\n                \"Alt+Tab\"\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/label.rs",
    "content": "use std::ops::Range;\n\nuse gpui::{\n    div, prelude::FluentBuilder, rems, App, HighlightStyle, IntoElement, ParentElement, RenderOnce,\n    SharedString, StyleRefinement, Styled, StyledText, Window,\n};\n\nuse crate::{ActiveTheme, StyledExt};\n\nconst MASKED: &'static str = \"•\";\n\n/// Represents the type of match for highlighting text in a label.\n#[derive(Clone)]\npub enum HighlightsMatch {\n    Prefix(SharedString),\n    Full(SharedString),\n}\n\nimpl HighlightsMatch {\n    pub fn as_str(&self) -> &str {\n        match self {\n            Self::Prefix(s) => s.as_str(),\n            Self::Full(s) => s.as_str(),\n        }\n    }\n\n    #[inline]\n    pub fn is_prefix(&self) -> bool {\n        matches!(self, Self::Prefix(_))\n    }\n}\n\nimpl From<&str> for HighlightsMatch {\n    fn from(value: &str) -> Self {\n        Self::Full(value.to_string().into())\n    }\n}\n\nimpl From<String> for HighlightsMatch {\n    fn from(value: String) -> Self {\n        Self::Full(value.into())\n    }\n}\n\nimpl From<SharedString> for HighlightsMatch {\n    fn from(value: SharedString) -> Self {\n        Self::Full(value)\n    }\n}\n\n/// A text label element with optional secondary text, masking, and highlighting capabilities.\n#[derive(IntoElement)]\npub struct Label {\n    style: StyleRefinement,\n    label: SharedString,\n    secondary: Option<SharedString>,\n    masked: bool,\n    highlights_text: Option<HighlightsMatch>,\n}\n\nimpl Label {\n    /// Create a new label with the main label.\n    pub fn new(label: impl Into<SharedString>) -> Self {\n        let label: SharedString = label.into();\n        Self {\n            style: Default::default(),\n            label,\n            secondary: None,\n            masked: false,\n            highlights_text: None,\n        }\n    }\n\n    /// Set the secondary text for the label,\n    /// the secondary text will be displayed after the label text with `muted` color.\n    pub fn secondary(mut self, secondary: impl Into<SharedString>) -> Self {\n        self.secondary = Some(secondary.into());\n        self\n    }\n\n    /// Set whether to mask the label text.\n    pub fn masked(mut self, masked: bool) -> Self {\n        self.masked = masked;\n        self\n    }\n\n    /// Set for matching text to highlight in the label.\n    pub fn highlights(mut self, text: impl Into<HighlightsMatch>) -> Self {\n        self.highlights_text = Some(text.into());\n        self\n    }\n\n    fn full_text(&self) -> SharedString {\n        match &self.secondary {\n            Some(secondary) => format!(\"{} {}\", self.label, secondary).into(),\n            None => self.label.clone(),\n        }\n    }\n\n    fn highlight_ranges(&self, total_length: usize) -> Vec<Range<usize>> {\n        let mut ranges = Vec::new();\n        let full_text = self.full_text();\n\n        if self.secondary.is_some() {\n            ranges.push(0..self.label.len());\n            ranges.push(self.label.len()..total_length);\n        }\n\n        if let Some(matched) = &self.highlights_text {\n            let matched_str = matched.as_str();\n            if !matched_str.is_empty() {\n                let search_lower = matched_str.to_lowercase();\n                let full_text_lower = full_text.to_lowercase();\n\n                if matched.is_prefix() {\n                    // For prefix matching, only check if the text starts with the search term\n                    if full_text_lower.starts_with(&search_lower) {\n                        ranges.push(0..matched_str.len());\n                    }\n                } else {\n                    // For full matching, find all occurrences\n                    let mut search_start = 0;\n                    while let Some(pos) = full_text_lower[search_start..].find(&search_lower) {\n                        let match_start = search_start + pos;\n                        let match_end = match_start + matched_str.len();\n\n                        if match_end <= full_text.len() {\n                            ranges.push(match_start..match_end);\n                        }\n\n                        search_start = match_start + 1;\n                        while !full_text.is_char_boundary(search_start)\n                            && search_start < full_text.len()\n                        {\n                            search_start += 1;\n                        }\n\n                        if search_start >= full_text.len() {\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n\n        ranges\n    }\n\n    fn measure_highlights(\n        &self,\n        length: usize,\n        cx: &mut App,\n    ) -> Option<Vec<(Range<usize>, HighlightStyle)>> {\n        let ranges = self.highlight_ranges(length);\n        if ranges.is_empty() {\n            return None;\n        }\n\n        let mut highlights = Vec::new();\n        let mut highlight_ranges_added = 0;\n\n        if self.secondary.is_some() {\n            highlights.push((ranges[0].clone(), HighlightStyle::default()));\n            highlights.push((\n                ranges[1].clone(),\n                HighlightStyle {\n                    color: Some(cx.theme().muted_foreground),\n                    ..Default::default()\n                },\n            ));\n            highlight_ranges_added = 2;\n        }\n\n        for range in ranges.iter().skip(highlight_ranges_added) {\n            highlights.push((\n                range.clone(),\n                HighlightStyle {\n                    color: Some(cx.theme().blue),\n                    ..Default::default()\n                },\n            ));\n        }\n\n        Some(gpui::combine_highlights(vec![], highlights).collect())\n    }\n}\n\nimpl Styled for Label {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Label {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let mut text = self.full_text();\n        let chars_count = text.chars().count();\n\n        if self.masked {\n            text = SharedString::from(MASKED.repeat(chars_count))\n        };\n\n        let highlights = self.measure_highlights(text.len(), cx);\n\n        div()\n            .line_height(rems(1.25))\n            .text_color(cx.theme().foreground)\n            .refine_style(&self.style)\n            .child(\n                StyledText::new(&text).when_some(highlights, |this, hl| this.with_highlights(hl)),\n            )\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_highlight_ranges() {\n        // Basic functionality\n\n        // No highlights\n        let label = Label::new(\"Hello World\");\n        let result = label.highlight_ranges(\"Hello World\".len());\n        assert_eq!(result, Vec::<Range<usize>>::new());\n\n        // Secondary text ranges only\n        let label = Label::new(\"Hello\").secondary(\"World\");\n        let total_length = \"Hello World\".len();\n        let result = label.highlight_ranges(total_length);\n        assert_eq!(result.len(), 2);\n        assert_eq!(result[0], 0..5); // \"Hello\"\n        assert_eq!(result[1], 5..11); // \" World\"\n\n        // Text highlighting\n\n        // Single match with case insensitive\n        let label = Label::new(\"Hello World\").highlights(\"WORLD\");\n        let result = label.highlight_ranges(\"Hello World\".len());\n        assert_eq!(result.len(), 1);\n        assert_eq!(result[0], 6..11); // \"World\"\n\n        // Multiple matches\n        let label = Label::new(\"Hello Hello Hello\").highlights(\"Hello\");\n        let result = label.highlight_ranges(\"Hello Hello Hello\".len());\n        assert_eq!(result.len(), 3);\n        assert_eq!(result[0], 0..5); // First \"Hello\"\n        assert_eq!(result[1], 6..11); // Second \"Hello\"\n        assert_eq!(result[2], 12..17); // Third \"Hello\"\n\n        // No match and empty search\n        let label = Label::new(\"Hello World\").highlights(\"xyz\");\n        let result = label.highlight_ranges(\"Hello World\".len());\n        assert_eq!(result, Vec::<Range<usize>>::new());\n\n        let label = Label::new(\"Hello World\").highlights(\"\");\n        let result = label.highlight_ranges(\"Hello World\".len());\n        assert_eq!(result, Vec::<Range<usize>>::new());\n\n        // Combined functionality\n\n        // Secondary + highlights in main text\n        let label = Label::new(\"Hello\").secondary(\"World\").highlights(\"llo\");\n        let total_length = \"Hello World\".len();\n        let result = label.highlight_ranges(total_length);\n        assert_eq!(result.len(), 3);\n        assert_eq!(result[0], 0..5); // Main text range\n        assert_eq!(result[1], 5..11); // Secondary text range\n        assert_eq!(result[2], 2..5); // \"llo\" in main text\n\n        // Highlight in secondary text\n        let label = Label::new(\"Hello\").secondary(\"World\").highlights(\"World\");\n        let total_length = \"Hello World\".len();\n        let result = label.highlight_ranges(total_length);\n        assert_eq!(result.len(), 3);\n        assert_eq!(result[0], 0..5); // Main text range\n        assert_eq!(result[1], 5..11); // Secondary text range\n        assert_eq!(result[2], 6..11); // \"World\" in secondary text\n\n        // Cross-boundary highlight\n        let label = Label::new(\"Hello\").secondary(\"World\").highlights(\"o W\");\n        let total_length = \"Hello World\".len();\n        let result = label.highlight_ranges(total_length);\n        assert_eq!(result.len(), 3);\n        assert_eq!(result[0], 0..5); // Main text range\n        assert_eq!(result[1], 5..11); // Secondary text range\n        assert_eq!(result[2], 4..7); // \"o W\" across boundary\n\n        // Edge cases\n\n        // Overlapping matches\n        let label = Label::new(\"aaaa\").highlights(\"aa\");\n        let result = label.highlight_ranges(\"aaaa\".len());\n        assert!(result.len() >= 2);\n        assert_eq!(result[0], 0..2); // First \"aa\"\n        assert_eq!(result[1], 1..3); // Overlapping \"aa\"\n\n        // Unicode text\n        let label = Label::new(\"你好世界，Hello World\").highlights(\"世界\");\n        let result = label.highlight_ranges(\"你好世界，Hello World\".len());\n        assert_eq!(result.len(), 1);\n        let text = \"你好世界，Hello World\";\n        let start = text.find(\"世界\").unwrap();\n        let end = start + \"世界\".len();\n        assert_eq!(result[0], start..end);\n    }\n\n    #[test]\n    fn test_highlight_ranges_prefix() {\n        // Test prefix match - should only match the first occurrence\n        let label = Label::new(\"aaaa\").highlights(HighlightsMatch::Prefix(\"aa\".into()));\n        let result = label.highlight_ranges(\"aaaa\".len());\n        assert_eq!(result.len(), 1);\n        assert_eq!(result[0], 0..2); // Only first \"aa\"\n\n        // Test prefix vs full match behavior\n        let label_full =\n            Label::new(\"Hello Hello\").highlights(HighlightsMatch::Full(\"Hello\".into()));\n        let result_full = label_full.highlight_ranges(\"Hello Hello\".len());\n        assert_eq!(result_full.len(), 2); // Both \"Hello\" matches\n\n        let label_prefix =\n            Label::new(\"Hello Hello\").highlights(HighlightsMatch::Prefix(\"Hello\".into()));\n        let result_prefix = label_prefix.highlight_ranges(\"Hello Hello\".len());\n        assert_eq!(result_prefix.len(), 1); // Only first \"Hello\"\n        assert_eq!(result_prefix[0], 0..5);\n\n        // Test prefix with case insensitive matching\n        let label =\n            Label::new(\"Hello hello HELLO\").highlights(HighlightsMatch::Prefix(\"hello\".into()));\n        let result = label.highlight_ranges(\"Hello hello HELLO\".len());\n        assert_eq!(result.len(), 1);\n        assert_eq!(result[0], 0..5); // First \"Hello\" (case insensitive)\n\n        // Test prefix with no match\n        let label = Label::new(\"Hello World\").highlights(HighlightsMatch::Prefix(\"xyz\".into()));\n        let result = label.highlight_ranges(\"Hello World\".len());\n        assert_eq!(result.len(), 0);\n\n        // Test prefix with empty string\n        let label = Label::new(\"Hello World\").highlights(HighlightsMatch::Prefix(\"\".into()));\n        let result = label.highlight_ranges(\"Hello World\".len());\n        assert_eq!(result.len(), 0);\n\n        // Test prefix with secondary text - match in main text\n        let label = Label::new(\"Hello\")\n            .secondary(\"Hello World\")\n            .highlights(HighlightsMatch::Prefix(\"Hello\".into()));\n        let total_length = \"Hello Hello World\".len();\n        let result = label.highlight_ranges(total_length);\n        assert_eq!(result.len(), 3); // 2 for secondary + 1 for prefix match\n        assert_eq!(result[0], 0..5); // Main text range\n        assert_eq!(result[1], 5..17); // Secondary text range\n        assert_eq!(result[2], 0..5); // First \"Hello\" prefix match in main text\n\n        // Test prefix with secondary text - match spans boundary (now no match since \"abc\" is not at start of full text)\n        let label = Label::new(\"abc\")\n            .secondary(\"def abc def\")\n            .highlights(HighlightsMatch::Prefix(\"abc\".into()));\n        let total_length = \"abc def abc def\".len();\n        let result = label.highlight_ranges(total_length);\n        assert_eq!(result.len(), 3); // 2 for secondary + 1 for prefix match\n        assert_eq!(result[0], 0..3); // Main text range\n        assert_eq!(result[1], 3..15); // Secondary text range\n        assert_eq!(result[2], 0..3); // \"abc\" matches at start of full text\n\n        // Test prefix with Unicode characters\n        let label = Label::new(\"你好世界你好\").highlights(HighlightsMatch::Prefix(\"你好\".into()));\n        let result = label.highlight_ranges(\"你好世界你好\".len());\n        assert_eq!(result.len(), 1);\n        assert_eq!(result[0], 0..6); // First \"你好\" (6 bytes in UTF-8)\n\n        // Test prefix with overlapping pattern\n        let label = Label::new(\"abababab\").highlights(HighlightsMatch::Prefix(\"abab\".into()));\n        let result = label.highlight_ranges(\"abababab\".len());\n        assert_eq!(result.len(), 1);\n        assert_eq!(result[0], 0..4); // First \"abab\" only\n\n        // Test prefix match at different positions (now no match since \"Hello\" is not at start)\n        let label =\n            Label::new(\"xyz Hello abc Hello\").highlights(HighlightsMatch::Prefix(\"Hello\".into()));\n        let result = label.highlight_ranges(\"xyz Hello abc Hello\".len());\n        assert_eq!(result.len(), 0); // No match since \"Hello\" is not at the beginning\n\n        // Test is_prefix method\n        let prefix_match = HighlightsMatch::Prefix(\"test\".into());\n        let full_match = HighlightsMatch::Full(\"test\".into());\n        assert!(prefix_match.is_prefix());\n        assert!(!full_match.is_prefix());\n\n        // Test as_str method for prefix\n        let prefix_match = HighlightsMatch::Prefix(\"test\".into());\n        assert_eq!(prefix_match.as_str(), \"test\");\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/lib.rs",
    "content": "use gpui::{App, SharedString};\nuse std::ops::Deref;\n\nmod anchored;\nmod async_util;\nmod element_ext;\nmod event;\nmod focus_trap;\nmod geometry;\npub mod global_state;\nmod icon;\nmod index_path;\n#[cfg(any(feature = \"inspector\", debug_assertions))]\nmod inspector;\nmod root;\nmod styled;\nmod time;\nmod title_bar;\nmod virtual_list;\nmod window_border;\nmod window_ext;\n\npub(crate) mod actions;\n\npub mod accordion;\npub mod alert;\npub mod animation;\npub mod avatar;\npub mod badge;\npub mod breadcrumb;\npub mod button;\npub mod chart;\npub mod checkbox;\npub mod clipboard;\npub mod collapsible;\npub mod color_picker;\npub mod description_list;\npub mod dialog;\npub mod divider;\npub mod dock;\npub mod form;\npub mod group_box;\npub mod highlighter;\npub mod history;\npub mod hover_card;\npub mod input;\npub mod kbd;\npub mod label;\npub mod link;\npub mod list;\npub mod menu;\npub mod notification;\npub mod pagination;\npub mod plot;\npub mod popover;\npub mod progress;\npub mod radio;\npub mod rating;\npub mod resizable;\npub mod scroll;\npub mod select;\npub mod setting;\npub mod sheet;\npub mod sidebar;\npub mod skeleton;\npub mod slider;\npub mod spinner;\npub mod stepper;\npub mod switch;\npub mod tab;\npub mod table;\npub mod tag;\npub mod text;\npub mod theme;\npub mod tooltip;\npub mod tree;\n\npub use crate::Disableable;\npub(crate) use anchored::*;\npub use element_ext::*;\npub use event::InteractiveElementExt;\npub use focus_trap::FocusTrapElement;\npub use geometry::*;\npub use global_state::GlobalState;\npub use gpui_component_macros::icon_named;\npub use icon::*;\npub use index_path::IndexPath;\npub use input::{Rope, RopeExt, RopeLines};\n#[cfg(any(feature = \"inspector\", debug_assertions))]\npub use inspector::*;\npub use root::Root;\npub use styled::*;\npub use theme::*;\npub use time::{calendar, date_picker};\npub use title_bar::*;\npub use virtual_list::{VirtualList, VirtualListScrollHandle, h_virtual_list, v_virtual_list};\npub use window_border::{WindowBorder, window_border, window_paddings};\npub use window_ext::WindowExt;\n\nrust_i18n::i18n!(\"locales\", fallback = \"en\");\n\n/// Initialize the components.\n///\n/// You must initialize the components at your application's entry point.\npub fn init(cx: &mut App) {\n    theme::init(cx);\n    global_state::init(cx);\n    #[cfg(any(feature = \"inspector\", debug_assertions))]\n    inspector::init(cx);\n    root::init(cx);\n    focus_trap::init(cx);\n    color_picker::init(cx);\n    date_picker::init(cx);\n    dock::init(cx);\n    sheet::init(cx);\n    select::init(cx);\n    input::init(cx);\n    list::init(cx);\n    dialog::init(cx);\n    popover::init(cx);\n    menu::init(cx);\n    table::init(cx);\n    text::init(cx);\n    tree::init(cx);\n}\n\n#[inline]\npub fn locale() -> impl Deref<Target = str> {\n    rust_i18n::locale()\n}\n\n#[inline]\npub fn set_locale(locale: &str) {\n    rust_i18n::set_locale(locale)\n}\n\n#[inline]\npub(crate) fn measure_enable() -> bool {\n    std::env::var(\"ZED_MEASUREMENTS\").is_ok() || std::env::var(\"GPUI_MEASUREMENTS\").is_ok()\n}\n\n/// Measures the execution time of a function and logs it if `if_` is true.\n///\n/// And need env `GPUI_MEASUREMENTS=1`\n#[inline]\n#[track_caller]\npub fn measure_if(name: impl Into<SharedString>, if_: bool, f: impl FnOnce()) {\n    if if_ && measure_enable() {\n        let measure = Measure::new(name);\n        f();\n        measure.end();\n    } else {\n        f();\n    }\n}\n\n/// Measures the execution time.\n#[inline]\n#[track_caller]\npub fn measure(name: impl Into<SharedString>, f: impl FnOnce()) {\n    measure_if(name, true, f);\n}\n\npub struct Measure {\n    name: SharedString,\n    start: std::time::Instant,\n}\n\nimpl Measure {\n    #[track_caller]\n    pub fn new(name: impl Into<SharedString>) -> Self {\n        Self {\n            name: name.into(),\n            start: std::time::Instant::now(),\n        }\n    }\n\n    #[track_caller]\n    pub fn end(self) {\n        let duration = self.start.elapsed();\n        tracing::trace!(\"{} in {:?}\", self.name, duration);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/link.rs",
    "content": "use gpui::{\n    div, AnyElement, ClickEvent, ElementId, InteractiveElement, IntoElement, MouseButton,\n    ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled,\n};\n\nuse crate::{ActiveTheme as _, StyledExt};\n\n/// A Link element like a `<a>` tag in HTML.\n#[derive(IntoElement)]\npub struct Link {\n    id: ElementId,\n    style: StyleRefinement,\n    href: Option<SharedString>,\n    disabled: bool,\n    on_click: Option<Box<dyn Fn(&ClickEvent, &mut gpui::Window, &mut gpui::App) + 'static>>,\n    children: Vec<AnyElement>,\n}\n\nimpl Link {\n    /// Create a new Link element.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default(),\n            href: None,\n            on_click: None,\n            disabled: false,\n            children: Vec::new(),\n        }\n    }\n\n    /// Set the href of the link.\n    pub fn href(mut self, href: impl Into<SharedString>) -> Self {\n        self.href = Some(href.into());\n        self\n    }\n\n    /// Set the click handler of the link.\n    ///\n    /// If this set, the handler will be called when the link is clicked.\n    /// Otherwise, the link will only open the href if set.\n    pub fn on_click(\n        mut self,\n        handler: impl Fn(&ClickEvent, &mut gpui::Window, &mut gpui::App) + 'static,\n    ) -> Self {\n        self.on_click = Some(Box::new(handler));\n        self\n    }\n\n    /// Set the disabled state, default false.\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl Styled for Link {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl ParentElement for Link {\n    fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {\n        self.children.extend(elements)\n    }\n}\n\nimpl RenderOnce for Link {\n    fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {\n        let href = self.href.clone();\n        let on_click = self.on_click;\n\n        div()\n            .id(self.id)\n            .text_color(cx.theme().link)\n            .text_decoration_1()\n            .text_decoration_color(cx.theme().link)\n            .hover(|this| {\n                this.text_color(cx.theme().link.opacity(0.8))\n                    .text_decoration_1()\n            })\n            .active(|this| {\n                this.text_color(cx.theme().link.opacity(0.6))\n                    .text_decoration_1()\n            })\n            .cursor_pointer()\n            .refine_style(&self.style)\n            .on_mouse_down(MouseButton::Left, |_, _, cx| {\n                cx.stop_propagation();\n            })\n            .on_click({\n                move |e, window, cx| {\n                    if let Some(href) = &href {\n                        cx.open_url(&href.clone());\n                    }\n                    if let Some(on_click) = &on_click {\n                        on_click(e, window, cx);\n                    }\n                }\n            })\n            .children(self.children)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/list/cache.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{App, Pixels, Size};\n\nuse crate::IndexPath;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub(crate) enum RowEntry {\n    Entry(IndexPath),\n    SectionHeader(usize),\n    SectionFooter(usize),\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\npub(crate) struct MeasuredEntrySize {\n    pub(crate) item_size: Size<Pixels>,\n    pub(crate) section_header_size: Size<Pixels>,\n    pub(crate) section_footer_size: Size<Pixels>,\n}\n\nimpl RowEntry {\n    #[inline]\n    #[allow(unused)]\n    pub(crate) fn is_section_header(&self) -> bool {\n        matches!(self, RowEntry::SectionHeader(_))\n    }\n\n    pub(crate) fn eq_index_path(&self, path: &IndexPath) -> bool {\n        match self {\n            RowEntry::Entry(index_path) => index_path == path,\n            RowEntry::SectionHeader(_) | RowEntry::SectionFooter(_) => false,\n        }\n    }\n\n    #[allow(unused)]\n    pub(crate) fn index(&self) -> IndexPath {\n        match self {\n            RowEntry::Entry(index_path) => *index_path,\n            RowEntry::SectionHeader(ix) => IndexPath::default().section(*ix),\n            RowEntry::SectionFooter(ix) => IndexPath::default().section(*ix),\n        }\n    }\n\n    #[inline]\n    #[allow(unused)]\n    pub(crate) fn is_section_footer(&self) -> bool {\n        matches!(self, RowEntry::SectionFooter(_))\n    }\n\n    #[inline]\n    pub(crate) fn is_entry(&self) -> bool {\n        matches!(self, RowEntry::Entry(_))\n    }\n\n    #[inline]\n    #[allow(unused)]\n    pub(crate) fn section_ix(&self) -> Option<usize> {\n        match self {\n            RowEntry::SectionHeader(ix) | RowEntry::SectionFooter(ix) => Some(*ix),\n            _ => None,\n        }\n    }\n}\n\n#[derive(Default, Clone)]\npub(crate) struct RowsCache {\n    /// Only have section's that have rows.\n    pub(crate) entities: Rc<Vec<RowEntry>>,\n    pub(crate) items_count: usize,\n    /// The sections, the item is number of rows in each section.\n    pub(crate) sections: Rc<Vec<usize>>,\n    pub(crate) entries_sizes: Rc<Vec<Size<Pixels>>>,\n    measured_size: MeasuredEntrySize,\n}\n\nimpl RowsCache {\n    pub(crate) fn get(&self, flatten_ix: usize) -> Option<RowEntry> {\n        self.entities.get(flatten_ix).cloned()\n    }\n\n    /// Returns the number of flattened rows (Includes header, item, footer).\n    pub(crate) fn len(&self) -> usize {\n        self.entities.len()\n    }\n\n    /// Return the number of items in the cache.\n    pub(crate) fn items_count(&self) -> usize {\n        self.items_count\n    }\n\n    /// Returns the index of the  Entry with given path in the flattened rows.\n    pub(crate) fn position_of(&self, path: &IndexPath) -> Option<usize> {\n        self.entities\n            .iter()\n            .position(|p| p.is_entry() && p.eq_index_path(path))\n    }\n\n    /// Return prev row, if the row is the first in the first section, goes to the last row.\n    ///\n    /// Empty rows section are skipped.\n    pub(crate) fn prev(&self, path: Option<IndexPath>) -> IndexPath {\n        let path = path.unwrap_or_default();\n        let Some(pos) = self.position_of(&path) else {\n            return self\n                .entities\n                .iter()\n                .rfind(|entry| entry.is_entry())\n                .map(|entry| entry.index())\n                .unwrap_or_default();\n        };\n\n        if let Some(path) = self\n            .entities\n            .iter()\n            .take(pos)\n            .rev()\n            .find(|entry| entry.is_entry())\n            .map(|entry| entry.index())\n        {\n            path\n        } else {\n            self.entities\n                .iter()\n                .rfind(|entry| entry.is_entry())\n                .map(|entry| entry.index())\n                .unwrap_or_default()\n        }\n    }\n\n    /// Returns the next row, if the row is the last in the last section, goes to the first row.\n    ///\n    /// Empty rows section are skipped.\n    pub(crate) fn next(&self, path: Option<IndexPath>) -> IndexPath {\n        let Some(mut path) = path else {\n            return IndexPath::default();\n        };\n\n        let Some(pos) = self.position_of(&path) else {\n            return self\n                .entities\n                .iter()\n                .find(|entry| entry.is_entry())\n                .map(|entry| entry.index())\n                .unwrap_or_default();\n        };\n\n        if let Some(next_path) = self\n            .entities\n            .iter()\n            .skip(pos + 1)\n            .find(|entry| entry.is_entry())\n            .map(|entry| entry.index())\n        {\n            path = next_path;\n        } else {\n            path = self\n                .entities\n                .iter()\n                .find(|entry| entry.is_entry())\n                .map(|entry| entry.index())\n                .unwrap_or_default()\n        }\n\n        path\n    }\n\n    pub(crate) fn prepare_if_needed<F>(\n        &mut self,\n        sections_count: usize,\n        measured_size: MeasuredEntrySize,\n        cx: &App,\n        rows_count_f: F,\n    ) where\n        F: Fn(usize, &App) -> usize,\n    {\n        let mut new_sections = vec![];\n        for section_ix in 0..sections_count {\n            new_sections.push(rows_count_f(section_ix, cx));\n        }\n\n        let need_update = new_sections != *self.sections || self.measured_size != measured_size;\n\n        if !need_update {\n            return;\n        }\n\n        let mut entries_sizes = vec![];\n        let mut total_items_count = 0;\n        self.measured_size = measured_size;\n        self.sections = Rc::new(new_sections);\n        self.entities = Rc::new(\n            self.sections\n                .iter()\n                .enumerate()\n                .flat_map(|(section, items_count)| {\n                    total_items_count += items_count;\n                    let mut children = vec![];\n                    if *items_count == 0 {\n                        return children;\n                    }\n\n                    children.push(RowEntry::SectionHeader(section));\n                    entries_sizes.push(measured_size.section_header_size);\n                    for row in 0..*items_count {\n                        children.push(RowEntry::Entry(IndexPath {\n                            section,\n                            row,\n                            ..Default::default()\n                        }));\n                        entries_sizes.push(measured_size.item_size);\n                    }\n                    children.push(RowEntry::SectionFooter(section));\n                    entries_sizes.push(measured_size.section_footer_size);\n                    children\n                })\n                .collect(),\n        );\n        self.entries_sizes = Rc::new(entries_sizes);\n        self.items_count = total_items_count;\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::rc::Rc;\n\n    use crate::{\n        IndexPath,\n        list::cache::{RowEntry, RowsCache},\n    };\n\n    fn build_entities(sections: &[usize]) -> Vec<RowEntry> {\n        sections\n            .iter()\n            .enumerate()\n            .flat_map(|(section, items_count)| {\n                let mut children = vec![];\n                if *items_count == 0 {\n                    return children;\n                }\n\n                children.push(RowEntry::SectionHeader(section));\n                for row in 0..*items_count {\n                    children.push(RowEntry::Entry(IndexPath {\n                        section,\n                        row,\n                        ..Default::default()\n                    }));\n                }\n                children.push(RowEntry::SectionFooter(section));\n                children\n            })\n            .collect()\n    }\n\n    #[test]\n    fn test_prev_next() {\n        let mut row_cache = RowsCache::default();\n        // section 0\n        //  row 0\n        //  row 1\n        // section 1\n        //  row 0\n        //  row 1\n        //  row 2\n        //  row 3\n        // section 2\n        //  row 0\n        //  row 1\n        //  row 2\n        row_cache.sections = Rc::new(vec![2, 4, 3]);\n        row_cache.entities = Rc::new(build_entities(&[2, 4, 3]));\n\n        assert_eq!(\n            row_cache.next(Some(IndexPath::new(0).section(0))),\n            IndexPath::new(1).section(0)\n        );\n        assert_eq!(\n            row_cache.next(Some(IndexPath::new(1).section(0))),\n            IndexPath::new(0).section(1)\n        );\n        assert_eq!(\n            row_cache.next(Some(IndexPath::new(0).section(1))),\n            IndexPath::new(1).section(1)\n        );\n        assert_eq!(\n            row_cache.next(Some(IndexPath::new(3).section(1))),\n            IndexPath::new(0).section(2)\n        );\n        assert_eq!(\n            row_cache.next(Some(IndexPath::new(0).section(2))),\n            IndexPath::new(1).section(2)\n        );\n        assert_eq!(\n            row_cache.next(Some(IndexPath::new(1).section(2))),\n            IndexPath::new(2).section(2)\n        );\n        assert_eq!(\n            row_cache.next(Some(IndexPath::new(2).section(2))),\n            IndexPath::new(0).section(0)\n        );\n\n        assert_eq!(\n            row_cache.prev(Some(IndexPath::new(0).section(0))),\n            IndexPath::new(2).section(2)\n        );\n        assert_eq!(\n            row_cache.prev(Some(IndexPath::new(1).section(0))),\n            IndexPath::new(0).section(0)\n        );\n        assert_eq!(\n            row_cache.prev(Some(IndexPath::new(0).section(1))),\n            IndexPath::new(1).section(0)\n        );\n        assert_eq!(\n            row_cache.prev(Some(IndexPath::new(1).section(1))),\n            IndexPath::new(0).section(1)\n        );\n        assert_eq!(\n            row_cache.prev(Some(IndexPath::new(3).section(1))),\n            IndexPath::new(2).section(1)\n        );\n        assert_eq!(\n            row_cache.prev(Some(IndexPath::new(0).section(2))),\n            IndexPath::new(3).section(1)\n        );\n    }\n\n    #[test]\n    fn test_prev_next_with_empty_sections() {\n        let mut row_cache = RowsCache::default();\n        // section 0: 2 items\n        // section 1: 0 items (empty, should be skipped)\n        // section 2: 3 items\n        // section 3: 0 items (empty, should be skipped)\n        // section 4: 1 item\n        row_cache.sections = Rc::new(vec![2, 0, 3, 0, 1]);\n        row_cache.entities = Rc::new(build_entities(&[2, 0, 3, 0, 1]));\n\n        // Test next: should skip empty sections\n        assert_eq!(\n            row_cache.next(Some(IndexPath::new(0).section(0))),\n            IndexPath::new(1).section(0)\n        );\n        assert_eq!(\n            row_cache.next(Some(IndexPath::new(1).section(0))),\n            IndexPath::new(0).section(2) // Skip section 1 (empty)\n        );\n        assert_eq!(\n            row_cache.next(Some(IndexPath::new(0).section(2))),\n            IndexPath::new(1).section(2)\n        );\n        assert_eq!(\n            row_cache.next(Some(IndexPath::new(2).section(2))),\n            IndexPath::new(0).section(4) // Skip section 3 (empty)\n        );\n        assert_eq!(\n            row_cache.next(Some(IndexPath::new(0).section(4))),\n            IndexPath::new(0).section(0) // Wrap around to first item\n        );\n\n        // Test prev: should skip empty sections\n        assert_eq!(\n            row_cache.prev(Some(IndexPath::new(0).section(0))),\n            IndexPath::new(0).section(4) // Wrap around to last item, skip empty sections\n        );\n        assert_eq!(\n            row_cache.prev(Some(IndexPath::new(0).section(2))),\n            IndexPath::new(1).section(0) // Skip section 1 (empty)\n        );\n        assert_eq!(\n            row_cache.prev(Some(IndexPath::new(0).section(4))),\n            IndexPath::new(2).section(2) // Skip section 3 (empty)\n        );\n        assert_eq!(\n            row_cache.prev(Some(IndexPath::new(1).section(2))),\n            IndexPath::new(0).section(2)\n        );\n        assert_eq!(\n            row_cache.prev(Some(IndexPath::new(2).section(2))),\n            IndexPath::new(1).section(2)\n        );\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/list/delegate.rs",
    "content": "use gpui::{AnyElement, App, Context, IntoElement, ParentElement as _, Styled as _, Task, Window};\n\nuse crate::{\n    ActiveTheme as _, Icon, IconName, IndexPath, Selectable, h_flex,\n    list::{ListState, loading::Loading},\n};\n\n/// A delegate for the List.\n#[allow(unused)]\npub trait ListDelegate: Sized + 'static {\n    type Item: Selectable + IntoElement;\n\n    /// When Query Input change, this method will be called.\n    /// You can perform search here.\n    fn perform_search(\n        &mut self,\n        query: &str,\n        window: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> Task<()> {\n        Task::ready(())\n    }\n\n    /// Return the number of sections in the list, default is 1.\n    ///\n    /// Min value is 1.\n    fn sections_count(&self, cx: &App) -> usize {\n        1\n    }\n\n    /// Return the number of items in the section at the given index.\n    ///\n    /// NOTE: Only the sections with items_count > 0 will be rendered. If the section has 0 items,\n    /// the section header and footer will also be skipped.\n    fn items_count(&self, section: usize, cx: &App) -> usize;\n\n    /// Render the item at the given index.\n    ///\n    /// Return None will skip the item.\n    ///\n    /// NOTE: Every item should have same height.\n    fn render_item(\n        &mut self,\n        ix: IndexPath,\n        window: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> Option<Self::Item>;\n\n    /// Render the section header at the given index, default is None.\n    ///\n    /// NOTE: Every header should have same height.\n    fn render_section_header(\n        &mut self,\n        section: usize,\n        window: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> Option<impl IntoElement> {\n        None::<AnyElement>\n    }\n\n    /// Render the section footer at the given index, default is None.\n    ///\n    /// NOTE: Every footer should have same height.\n    fn render_section_footer(\n        &mut self,\n        section: usize,\n        window: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> Option<impl IntoElement> {\n        None::<AnyElement>\n    }\n\n    /// Return a Element to show when list is empty.\n    fn render_empty(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> impl IntoElement {\n        h_flex()\n            .size_full()\n            .justify_center()\n            .text_color(cx.theme().muted_foreground.opacity(0.6))\n            .child(Icon::new(IconName::Inbox).size_12())\n            .into_any_element()\n    }\n\n    /// Returns Some(AnyElement) to render the initial state of the list.\n    ///\n    /// This can be used to show a view for the list before the user has\n    /// interacted with it.\n    ///\n    /// For example: The last search results, or the last selected item.\n    ///\n    /// Default is None, that means no initial state.\n    fn render_initial(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> Option<AnyElement> {\n        None\n    }\n\n    /// Returns the loading state to show the loading view.\n    fn loading(&self, cx: &App) -> bool {\n        false\n    }\n\n    /// Returns a Element to show when loading, default is built-in Skeleton\n    /// loading view.\n    fn render_loading(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> impl IntoElement {\n        Loading\n    }\n\n    /// Set the selected index, just store the ix, don't confirm.\n    fn set_selected_index(\n        &mut self,\n        ix: Option<IndexPath>,\n        window: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    );\n\n    /// Set the index of the item that has been right clicked.\n    fn set_right_clicked_index(\n        &mut self,\n        ix: Option<IndexPath>,\n        window: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) {\n    }\n\n    /// Set the confirm and give the selected index,\n    /// this is means user have clicked the item or pressed Enter.\n    ///\n    /// This will always to `set_selected_index` before confirm.\n    fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<ListState<Self>>) {\n    }\n\n    /// Cancel the selection, e.g.: Pressed ESC.\n    fn cancel(&mut self, window: &mut Window, cx: &mut Context<ListState<Self>>) {}\n\n    /// Return true to enable load more data when scrolling to the bottom.\n    ///\n    /// Default: false\n    fn has_more(&self, cx: &App) -> bool {\n        false\n    }\n\n    /// Returns a threshold value (n entities), of course,\n    /// when scrolling to the bottom, the remaining number of rows\n    /// triggers `load_more`.\n    ///\n    /// This should smaller than the total number of first load rows.\n    ///\n    /// Default: 20 entities (section header, footer and row)\n    fn load_more_threshold(&self) -> usize {\n        20\n    }\n\n    /// Load more data when the table is scrolled to the bottom.\n    ///\n    /// This will performed in a background task.\n    ///\n    /// This is always called when the table is near the bottom,\n    /// so you must check if there is more data to load or lock\n    /// the loading state.\n    fn load_more(&mut self, window: &mut Window, cx: &mut Context<ListState<Self>>) {}\n}\n"
  },
  {
    "path": "crates/ui/src/list/list.rs",
    "content": "use std::ops::Range;\nuse instant::Duration;\n\nuse crate::actions::{Cancel, Confirm, SelectDown, SelectUp};\nuse crate::input::InputState;\nuse crate::list::cache::{MeasuredEntrySize, RowEntry, RowsCache};\nuse crate::{\n    ActiveTheme, IconName, Size,\n    input::{Input, InputEvent},\n    scroll::Scrollbar,\n    v_flex,\n};\nuse crate::{Icon, IndexPath, Selectable, Sizable, StyledExt};\nuse crate::{VirtualListScrollHandle, list::ListDelegate, v_virtual_list};\nuse gpui::{\n    App, AvailableSpace, ClickEvent, Context, DefiniteLength, EdgesRefinement, EventEmitter,\n    ListSizingBehavior, RenderOnce, ScrollStrategy, SharedString, StatefulInteractiveElement,\n    StyleRefinement, Subscription, px, size,\n};\nuse gpui::{\n    AppContext, Entity, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding,\n    Length, MouseButton, ParentElement, Render, Styled, Task, Window, div, prelude::FluentBuilder,\n};\nuse rust_i18n::t;\n\npub(crate) fn init(cx: &mut App) {\n    let context: Option<&str> = Some(\"List\");\n    cx.bind_keys([\n        KeyBinding::new(\"escape\", Cancel, context),\n        KeyBinding::new(\"enter\", Confirm { secondary: false }, context),\n        KeyBinding::new(\"secondary-enter\", Confirm { secondary: true }, context),\n        KeyBinding::new(\"up\", SelectUp, context),\n        KeyBinding::new(\"down\", SelectDown, context),\n    ]);\n}\n\n#[derive(Clone)]\npub enum ListEvent {\n    /// Move to select item.\n    Select(IndexPath),\n    /// Click on item or pressed Enter.\n    Confirm(IndexPath),\n    /// Pressed ESC to deselect the item.\n    Cancel,\n}\n\nstruct ListOptions {\n    size: Size,\n    scrollbar_visible: bool,\n    search_placeholder: Option<SharedString>,\n    max_height: Option<Length>,\n    paddings: EdgesRefinement<DefiniteLength>,\n}\n\nimpl Default for ListOptions {\n    fn default() -> Self {\n        Self {\n            size: Size::default(),\n            scrollbar_visible: true,\n            max_height: None,\n            search_placeholder: None,\n            paddings: EdgesRefinement::default(),\n        }\n    }\n}\n\n/// The state for List.\n///\n/// List required all items has the same height.\npub struct ListState<D: ListDelegate> {\n    pub(crate) focus_handle: FocusHandle,\n    pub(crate) query_input: Entity<InputState>,\n    options: ListOptions,\n    delegate: D,\n    last_query: Option<String>,\n    scroll_handle: VirtualListScrollHandle,\n    rows_cache: RowsCache,\n    selected_index: Option<IndexPath>,\n    item_to_measure_index: IndexPath,\n    deferred_scroll_to_index: Option<(IndexPath, ScrollStrategy)>,\n    mouse_right_clicked_index: Option<IndexPath>,\n    reset_on_cancel: bool,\n    searchable: bool,\n    selectable: bool,\n    _search_task: Task<()>,\n    _load_more_task: Task<()>,\n    _query_input_subscription: Subscription,\n}\n\nimpl<D> ListState<D>\nwhere\n    D: ListDelegate,\n{\n    pub fn new(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let query_input =\n            cx.new(|cx| InputState::new(window, cx).placeholder(t!(\"List.search_placeholder\")));\n\n        let _query_input_subscription =\n            cx.subscribe_in(&query_input, window, Self::on_query_input_event);\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            options: ListOptions::default(),\n            delegate,\n            rows_cache: RowsCache::default(),\n            query_input,\n            last_query: None,\n            selected_index: None,\n            selectable: true,\n            searchable: false,\n            item_to_measure_index: IndexPath::default(),\n            deferred_scroll_to_index: None,\n            mouse_right_clicked_index: None,\n            scroll_handle: VirtualListScrollHandle::new(),\n            reset_on_cancel: true,\n            _search_task: Task::ready(()),\n            _load_more_task: Task::ready(()),\n            _query_input_subscription,\n        }\n    }\n\n    /// Sets whether the list is searchable, default is `false`.\n    ///\n    /// When `true`, there will be a search input at the top of the list.\n    pub fn searchable(mut self, searchable: bool) -> Self {\n        self.searchable = searchable;\n        self\n    }\n\n    pub fn set_searchable(&mut self, searchable: bool, cx: &mut Context<Self>) {\n        self.searchable = searchable;\n        cx.notify();\n    }\n\n    /// Sets whether the list is selectable, default is true.\n    pub fn selectable(mut self, selectable: bool) -> Self {\n        self.selectable = selectable;\n        self\n    }\n\n    /// Sets whether the list is selectable, default is true.\n    pub fn set_selectable(&mut self, selectable: bool, cx: &mut Context<Self>) {\n        self.selectable = selectable;\n        cx.notify();\n    }\n\n    pub fn delegate(&self) -> &D {\n        &self.delegate\n    }\n\n    pub fn delegate_mut(&mut self) -> &mut D {\n        &mut self.delegate\n    }\n\n    /// Focus the list, if the list is searchable, focus the search input.\n    pub fn focus(&mut self, window: &mut Window, cx: &mut App) {\n        self.focus_handle(cx).focus(window, cx);\n    }\n\n    /// Return true if either the list or the search input is focused.\n    pub(crate) fn is_focused(&self, window: &Window, cx: &App) -> bool {\n        self.focus_handle.is_focused(window) || self.query_input.focus_handle(cx).is_focused(window)\n    }\n\n    /// Set the selected index of the list,\n    /// this will also scroll to the selected item.\n    pub(crate) fn _set_selected_index(\n        &mut self,\n        ix: Option<IndexPath>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if !self.selectable {\n            return;\n        }\n\n        self.selected_index = ix;\n        self.delegate.set_selected_index(ix, window, cx);\n        self.scroll_to_selected_item(window, cx);\n    }\n\n    /// Set the selected index of the list,\n    /// this method will not scroll to the selected item.\n    pub fn set_selected_index(\n        &mut self,\n        ix: Option<IndexPath>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.selected_index = ix;\n        self.delegate.set_selected_index(ix, window, cx);\n    }\n\n    pub fn selected_index(&self) -> Option<IndexPath> {\n        self.selected_index\n    }\n\n    /// Set the index of the item that has been right clicked.\n    pub fn set_right_clicked_index(\n        &mut self,\n        ix: Option<IndexPath>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.mouse_right_clicked_index = ix;\n        self.delegate.set_right_clicked_index(ix, window, cx);\n    }\n\n    /// Returns the index of the item that has been right clicked.\n    pub fn right_clicked_index(&self) -> Option<IndexPath> {\n        self.mouse_right_clicked_index\n    }\n\n    /// Set a specific list item for measurement.\n    pub fn set_item_to_measure_index(\n        &mut self,\n        ix: IndexPath,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.item_to_measure_index = ix;\n        cx.notify();\n    }\n\n    /// Scroll to the item at the given index.\n    pub fn scroll_to_item(\n        &mut self,\n        ix: IndexPath,\n        strategy: ScrollStrategy,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if ix.section == 0 && ix.row == 0 {\n            // If the item is the first item, scroll to the top.\n            let mut offset = self.scroll_handle.base_handle().offset();\n            offset.y = px(0.);\n            self.scroll_handle.base_handle().set_offset(offset);\n            cx.notify();\n            return;\n        }\n        self.deferred_scroll_to_index = Some((ix, strategy));\n        cx.notify();\n    }\n\n    /// Get scroll handle\n    pub fn scroll_handle(&self) -> &VirtualListScrollHandle {\n        &self.scroll_handle\n    }\n\n    pub fn scroll_to_selected_item(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        if let Some(ix) = self.selected_index {\n            self.deferred_scroll_to_index = Some((ix, ScrollStrategy::Top));\n            cx.notify();\n        }\n    }\n\n    fn on_query_input_event(\n        &mut self,\n        state: &Entity<InputState>,\n        event: &InputEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        match event {\n            InputEvent::Change => {\n                let text = state.read(cx).value();\n                let text = text.trim().to_string();\n                if Some(&text) == self.last_query.as_ref() {\n                    return;\n                }\n\n                self.set_searching(true, window, cx);\n                let search = self.delegate.perform_search(&text, window, cx);\n\n                if self.rows_cache.len() > 0 {\n                    self._set_selected_index(Some(IndexPath::default()), window, cx);\n                } else {\n                    self._set_selected_index(None, window, cx);\n                }\n\n                self._search_task = cx.spawn_in(window, async move |this, window| {\n                    search.await;\n\n                    _ = this.update_in(window, |this, _, _| {\n                        this.scroll_handle.scroll_to_item(0, ScrollStrategy::Top);\n                        this.last_query = Some(text);\n                    });\n\n                    // Always wait 100ms to avoid flicker\n                    window.background_executor().timer(Duration::from_millis(100)).await;\n                    _ = this.update_in(window, |this, window, cx| {\n                        this.set_searching(false, window, cx);\n                    });\n                });\n            }\n            InputEvent::PressEnter { secondary } => self.on_action_confirm(\n                &Confirm {\n                    secondary: *secondary,\n                },\n                window,\n                cx,\n            ),\n            _ => {}\n        }\n    }\n\n    fn set_searching(&mut self, searching: bool, window: &mut Window, cx: &mut Context<Self>) {\n        self.query_input\n            .update(cx, |input, cx| input.set_loading(searching, window, cx));\n    }\n\n    /// Dispatch delegate's `load_more` method when the\n    /// visible range is near the end.\n    fn load_more_if_need(\n        &mut self,\n        entities_count: usize,\n        visible_end: usize,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        // FIXME: Here need void sections items count.\n\n        let threshold = self.delegate.load_more_threshold();\n        // Securely handle subtract logic to prevent attempt\n        // to subtract with overflow\n        if visible_end >= entities_count.saturating_sub(threshold) {\n            if !self.delegate.has_more(cx) {\n                return;\n            }\n\n            self._load_more_task = cx.spawn_in(window, async move |view, cx| {\n                _ = view.update_in(cx, |view, window, cx| {\n                    view.delegate.load_more(window, cx);\n                });\n            });\n        }\n    }\n\n    pub(crate) fn reset_on_cancel(mut self, reset: bool) -> Self {\n        self.reset_on_cancel = reset;\n        self\n    }\n\n    fn on_action_cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {\n        cx.propagate();\n        if self.reset_on_cancel {\n            self._set_selected_index(None, window, cx);\n        }\n\n        self.delegate.cancel(window, cx);\n        cx.emit(ListEvent::Cancel);\n        cx.notify();\n    }\n\n    fn on_action_confirm(\n        &mut self,\n        confirm: &Confirm,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if self.rows_cache.len() == 0 {\n            return;\n        }\n\n        let Some(ix) = self.selected_index else {\n            return;\n        };\n\n        self.delegate\n            .set_selected_index(self.selected_index, window, cx);\n        self.delegate.confirm(confirm.secondary, window, cx);\n        cx.emit(ListEvent::Confirm(ix));\n        cx.notify();\n    }\n\n    fn select_item(&mut self, ix: IndexPath, window: &mut Window, cx: &mut Context<Self>) {\n        if !self.selectable {\n            return;\n        }\n\n        self.selected_index = Some(ix);\n        self.delegate.set_selected_index(Some(ix), window, cx);\n        self.scroll_to_selected_item(window, cx);\n        cx.emit(ListEvent::Select(ix));\n        cx.notify();\n    }\n\n    pub(crate) fn on_action_select_prev(\n        &mut self,\n        _: &SelectUp,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if self.rows_cache.len() == 0 {\n            return;\n        }\n\n        let prev_ix = self.rows_cache.prev(self.selected_index);\n        self.select_item(prev_ix, window, cx);\n    }\n\n    pub(crate) fn on_action_select_next(\n        &mut self,\n        _: &SelectDown,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if self.rows_cache.len() == 0 {\n            return;\n        }\n\n        let next_ix = self.rows_cache.next(self.selected_index);\n        self.select_item(next_ix, window, cx);\n    }\n\n    fn prepare_items_if_needed(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        let sections_count = self.delegate.sections_count(cx).max(1);\n        let mut measured_size = MeasuredEntrySize::default();\n\n        // Measure the item_height and section header/footer height.\n        let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);\n        measured_size.item_size = self\n            .render_list_item(self.item_to_measure_index, window, cx)\n            .into_any_element()\n            .layout_as_root(available_space, window, cx);\n\n        if let Some(mut el) = self\n            .delegate\n            .render_section_header(0, window, cx)\n            .map(|r| r.into_any_element())\n        {\n            measured_size.section_header_size = el.layout_as_root(available_space, window, cx);\n        }\n        if let Some(mut el) = self\n            .delegate\n            .render_section_footer(0, window, cx)\n            .map(|r| r.into_any_element())\n        {\n            measured_size.section_footer_size = el.layout_as_root(available_space, window, cx);\n        }\n\n        self.rows_cache\n            .prepare_if_needed(sections_count, measured_size, cx, |section_ix, cx| {\n                self.delegate.items_count(section_ix, cx)\n            });\n    }\n\n    fn render_list_item(\n        &mut self,\n        ix: IndexPath,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> impl IntoElement {\n        let selectable = self.selectable;\n        let selected = self.selected_index.map(|s| s.eq_row(ix)).unwrap_or(false);\n        let mouse_right_clicked = self\n            .mouse_right_clicked_index\n            .map(|s| s.eq_row(ix))\n            .unwrap_or(false);\n        let id = SharedString::from(format!(\"list-item-{}\", ix));\n\n        div()\n            .id(id)\n            .w_full()\n            .relative()\n            .overflow_hidden()\n            .children(self.delegate.render_item(ix, window, cx).map(|item| {\n                item.selected(selected)\n                    .secondary_selected(mouse_right_clicked)\n            }))\n            .when(selectable, |this| {\n                this.on_click(cx.listener(move |this, e: &ClickEvent, window, cx| {\n                    this.set_right_clicked_index(None, window, cx);\n                    this.selected_index = Some(ix);\n                    this.on_action_confirm(\n                        &Confirm {\n                            secondary: e.modifiers().secondary(),\n                        },\n                        window,\n                        cx,\n                    );\n                }))\n                .on_mouse_down(\n                    MouseButton::Right,\n                    cx.listener(move |this, _, window, cx| {\n                        this.set_right_clicked_index(Some(ix), window, cx);\n                        cx.notify();\n                    }),\n                )\n            })\n    }\n\n    fn render_items(\n        &mut self,\n        items_count: usize,\n        entities_count: usize,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> impl IntoElement {\n        let rows_cache = self.rows_cache.clone();\n        let scrollbar_visible = self.options.scrollbar_visible;\n        let scroll_handle = self.scroll_handle.clone();\n\n        v_flex()\n            .flex_grow()\n            .relative()\n            .size_full()\n            .when_some(self.options.max_height, |this, h| this.max_h(h))\n            .overflow_hidden()\n            .when(items_count == 0, |this| {\n                this.child(self.delegate.render_empty(window, cx))\n            })\n            .when(items_count > 0, {\n                |this| {\n                    this.child(\n                        v_virtual_list(\n                            cx.entity(),\n                            \"virtual-list\",\n                            rows_cache.entries_sizes.clone(),\n                            move |list, visible_range: Range<usize>, window, cx| {\n                                list.load_more_if_need(\n                                    entities_count,\n                                    visible_range.end,\n                                    window,\n                                    cx,\n                                );\n\n                                // NOTE: Here the v_virtual_list would not able to have gap_y,\n                                // because the section header, footer is always have rendered as a empty child item,\n                                // even the delegate give a None result.\n\n                                visible_range\n                                    .map(|ix| {\n                                        let Some(entry) = rows_cache.get(ix) else {\n                                            return div();\n                                        };\n\n                                        div().children(match entry {\n                                            RowEntry::Entry(index) => Some(\n                                                list.render_list_item(index, window, cx)\n                                                    .into_any_element(),\n                                            ),\n                                            RowEntry::SectionHeader(section_ix) => list\n                                                .delegate_mut()\n                                                .render_section_header(section_ix, window, cx)\n                                                .map(|r| r.into_any_element()),\n                                            RowEntry::SectionFooter(section_ix) => list\n                                                .delegate_mut()\n                                                .render_section_footer(section_ix, window, cx)\n                                                .map(|r| r.into_any_element()),\n                                        })\n                                    })\n                                    .collect::<Vec<_>>()\n                            },\n                        )\n                        .paddings(self.options.paddings.clone())\n                        .when(self.options.max_height.is_some(), |this| {\n                            this.with_sizing_behavior(ListSizingBehavior::Infer)\n                        })\n                        .track_scroll(&scroll_handle)\n                        .into_any_element(),\n                    )\n                }\n            })\n            .when(scrollbar_visible, |this| {\n                this.child(Scrollbar::vertical(&scroll_handle))\n            })\n    }\n}\n\nimpl<D> Focusable for ListState<D>\nwhere\n    D: ListDelegate,\n{\n    fn focus_handle(&self, cx: &App) -> FocusHandle {\n        if self.searchable {\n            self.query_input.focus_handle(cx)\n        } else {\n            self.focus_handle.clone()\n        }\n    }\n}\nimpl<D> EventEmitter<ListEvent> for ListState<D> where D: ListDelegate {}\nimpl<D> Render for ListState<D>\nwhere\n    D: ListDelegate,\n{\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        self.prepare_items_if_needed(window, cx);\n\n        // Scroll to the selected item if it is set.\n        if let Some((ix, strategy)) = self.deferred_scroll_to_index.take() {\n            if let Some(item_ix) = self.rows_cache.position_of(&ix) {\n                self.scroll_handle.scroll_to_item(item_ix, strategy);\n            }\n        }\n\n        let loading = self.delegate().loading(cx);\n        let query_input = if self.searchable {\n            // sync placeholder\n            if let Some(placeholder) = &self.options.search_placeholder {\n                self.query_input.update(cx, |input, cx| {\n                    input.set_placeholder(placeholder.clone(), window, cx);\n                });\n            }\n            Some(self.query_input.clone())\n        } else {\n            None\n        };\n\n        let loading_view = if loading {\n            Some(self.delegate.render_loading(window, cx).into_any_element())\n        } else {\n            None\n        };\n        let initial_view = if let Some(input) = &query_input {\n            if input.read(cx).value().is_empty() {\n                self.delegate.render_initial(window, cx)\n            } else {\n                None\n            }\n        } else {\n            None\n        };\n        let items_count = self.rows_cache.items_count();\n        let entities_count = self.rows_cache.len();\n        let mouse_right_clicked_index = self.mouse_right_clicked_index;\n\n        v_flex()\n            .key_context(\"List\")\n            .id(\"list-state\")\n            .track_focus(&self.focus_handle)\n            .size_full()\n            .relative()\n            .overflow_hidden()\n            .when_some(query_input, |this, input| {\n                this.child(\n                    div()\n                        .map(|this| match self.options.size {\n                            Size::Small => this.px_1p5(),\n                            _ => this.px_2(),\n                        })\n                        .border_b_1()\n                        .border_color(cx.theme().border)\n                        .child(\n                            Input::new(&input)\n                                .with_size(self.options.size)\n                                .prefix(\n                                    Icon::new(IconName::Search)\n                                        .text_color(cx.theme().muted_foreground),\n                                )\n                                .cleanable(true)\n                                .p_0()\n                                .appearance(false),\n                        ),\n                )\n            })\n            .when(!loading, |this| {\n                this.on_action(cx.listener(Self::on_action_cancel))\n                    .on_action(cx.listener(Self::on_action_confirm))\n                    .on_action(cx.listener(Self::on_action_select_next))\n                    .on_action(cx.listener(Self::on_action_select_prev))\n                    .map(|this| {\n                        if let Some(view) = initial_view {\n                            this.child(view)\n                        } else {\n                            this.child(self.render_items(items_count, entities_count, window, cx))\n                        }\n                    })\n                    // Click out to cancel right clicked row\n                    .when(mouse_right_clicked_index.is_some(), |this| {\n                        this.on_mouse_down_out(cx.listener(|this, _, window, cx| {\n                            this.set_right_clicked_index(None, window, cx);\n                            cx.notify();\n                        }))\n                    })\n            })\n            .children(loading_view)\n    }\n}\n\n/// The List element.\n#[derive(IntoElement)]\npub struct List<D: ListDelegate + 'static> {\n    state: Entity<ListState<D>>,\n    style: StyleRefinement,\n    options: ListOptions,\n}\n\nimpl<D> List<D>\nwhere\n    D: ListDelegate + 'static,\n{\n    /// Create a new List element with the given ListState entity.\n    pub fn new(state: &Entity<ListState<D>>) -> Self {\n        Self {\n            state: state.clone(),\n            style: StyleRefinement::default(),\n            options: ListOptions::default(),\n        }\n    }\n\n    /// Set whether the scrollbar is visible, default is `true`.\n    pub fn scrollbar_visible(mut self, visible: bool) -> Self {\n        self.options.scrollbar_visible = visible;\n        self\n    }\n\n    /// Sets the placeholder text for the search input.\n    pub fn search_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {\n        self.options.search_placeholder = Some(placeholder.into());\n        self\n    }\n}\n\nimpl<D> Styled for List<D>\nwhere\n    D: ListDelegate + 'static,\n{\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl<D> Sizable for List<D>\nwhere\n    D: ListDelegate + 'static,\n{\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.options.size = size.into();\n        self\n    }\n}\n\nimpl<D> RenderOnce for List<D>\nwhere\n    D: ListDelegate + 'static,\n{\n    fn render(mut self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        // Take paddings, max_height to options, and clear them from style,\n        // because they would be applied to the inner virtual list.\n        self.options.paddings = self.style.padding.clone();\n        self.options.max_height = self.style.max_size.height;\n        self.style.padding = EdgesRefinement::default();\n        self.style.max_size.height = None;\n\n        self.state.update(cx, |state, _| {\n            state.options = self.options;\n        });\n\n        div()\n            .id(\"list\")\n            .size_full()\n            .refine_style(&self.style)\n            .child(self.state.clone())\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/list/list_item.rs",
    "content": "use crate::{ActiveTheme, Disableable, Icon, Selectable, Sizable as _, StyledExt, h_flex};\nuse gpui::{\n    AnyElement, App, ClickEvent, Div, ElementId, InteractiveElement, IntoElement, MouseMoveEvent,\n    ParentElement, RenderOnce, Stateful, StatefulInteractiveElement as _, StyleRefinement, Styled,\n    Window, div, prelude::FluentBuilder as _,\n};\nuse smallvec::SmallVec;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\nenum ListItemMode {\n    #[default]\n    Entry,\n    Separator,\n}\n\nimpl ListItemMode {\n    #[inline]\n    fn is_separator(&self) -> bool {\n        matches!(self, ListItemMode::Separator)\n    }\n}\n\n#[derive(IntoElement)]\npub struct ListItem {\n    base: Stateful<Div>,\n    mode: ListItemMode,\n    style: StyleRefinement,\n    disabled: bool,\n    selected: bool,\n    secondary_selected: bool,\n    confirmed: bool,\n    check_icon: Option<Icon>,\n    on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,\n    on_mouse_enter: Option<Box<dyn Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static>>,\n    suffix: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>,\n    children: SmallVec<[AnyElement; 2]>,\n}\n\nimpl ListItem {\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        let id: ElementId = id.into();\n        Self {\n            mode: ListItemMode::Entry,\n            base: h_flex().id(id),\n            style: StyleRefinement::default(),\n            disabled: false,\n            selected: false,\n            secondary_selected: false,\n            confirmed: false,\n            on_click: None,\n            on_mouse_enter: None,\n            check_icon: None,\n            suffix: None,\n            children: SmallVec::new(),\n        }\n    }\n\n    /// Set this list item to as a separator, it not able to be selected.\n    pub fn separator(mut self) -> Self {\n        self.mode = ListItemMode::Separator;\n        self\n    }\n\n    /// Set to show check icon, default is None.\n    pub fn check_icon(mut self, icon: impl Into<Icon>) -> Self {\n        self.check_icon = Some(icon.into());\n        self\n    }\n\n    /// Set ListItem as the selected item style.\n    pub fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    /// Set ListItem as the confirmed item style, it will show a check icon.\n    pub fn confirmed(mut self, confirmed: bool) -> Self {\n        self.confirmed = confirmed;\n        self\n    }\n\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    /// Set the suffix element of the input field, for example a clear button.\n    pub fn suffix<F, E>(mut self, builder: F) -> Self\n    where\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n        E: IntoElement,\n    {\n        self.suffix = Some(Box::new(move |window, cx| {\n            builder(window, cx).into_any_element()\n        }));\n        self\n    }\n\n    pub fn on_click(\n        mut self,\n        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_click = Some(Box::new(handler));\n        self\n    }\n\n    pub fn on_mouse_enter(\n        mut self,\n        handler: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_mouse_enter = Some(Box::new(handler));\n        self\n    }\n}\n\nimpl Disableable for ListItem {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl Selectable for ListItem {\n    fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        self.selected\n    }\n\n    fn secondary_selected(mut self, selected: bool) -> Self {\n        self.secondary_selected = selected;\n        self\n    }\n}\n\nimpl Styled for ListItem {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl ParentElement for ListItem {\n    fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl RenderOnce for ListItem {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let is_active = self.confirmed || self.selected;\n\n        let corner_radii = self.style.corner_radii.clone();\n\n        let mut selected_style = StyleRefinement::default();\n        selected_style.corner_radii = corner_radii;\n\n        let is_selectable = !(self.disabled || self.mode.is_separator());\n\n        self.base\n            .relative()\n            .gap_x_1()\n            .py_1()\n            .px_3()\n            .text_base()\n            .text_color(cx.theme().foreground)\n            .relative()\n            .items_center()\n            .justify_between()\n            .refine_style(&self.style)\n            .when(is_selectable, |this| {\n                this.when_some(self.on_click, |this, on_click| this.on_click(on_click))\n                    .when_some(self.on_mouse_enter, |this, on_mouse_enter| {\n                        this.on_mouse_move(move |ev, window, cx| (on_mouse_enter)(ev, window, cx))\n                    })\n                    .when(!is_active, |this| {\n                        this.hover(|this| this.bg(cx.theme().list_hover))\n                    })\n            })\n            .when(!is_selectable, |this| {\n                this.text_color(cx.theme().muted_foreground)\n            })\n            .child(\n                h_flex()\n                    .w_full()\n                    .items_center()\n                    .justify_between()\n                    .gap_x_1()\n                    .child(div().w_full().children(self.children))\n                    .when_some(self.check_icon, |this, icon| {\n                        this.child(\n                            div().w_5().items_center().justify_center().when(\n                                self.confirmed,\n                                |this| {\n                                    this.child(icon.small().text_color(cx.theme().muted_foreground))\n                                },\n                            ),\n                        )\n                    }),\n            )\n            .when_some(self.suffix, |this, suffix| this.child(suffix(window, cx)))\n            .map(|this| {\n                if is_selectable && (self.selected || self.secondary_selected) {\n                    let bg = if self.selected && cx.theme().list.active_highlight {\n                        cx.theme().list_active\n                    } else {\n                        cx.theme().accent\n                    };\n\n                    this.bg(bg).when(cx.theme().list.active_highlight, |this| {\n                        this.child(\n                            div()\n                                .absolute()\n                                .top_0()\n                                .left_0()\n                                .right_0()\n                                .bottom_0()\n                                .border_1()\n                                .border_color(cx.theme().list_active_border)\n                                .refine_style(&selected_style),\n                        )\n                    })\n                } else {\n                    this\n                }\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/list/loading.rs",
    "content": "use super::ListItem;\nuse crate::{skeleton::Skeleton, v_flex};\nuse gpui::{IntoElement, ParentElement as _, RenderOnce, Styled};\n\n#[derive(IntoElement)]\npub struct Loading;\n\n#[derive(IntoElement)]\nstruct LoadingItem;\n\nimpl RenderOnce for LoadingItem {\n    fn render(self, _window: &mut gpui::Window, _cx: &mut gpui::App) -> impl IntoElement {\n        ListItem::new(\"skeleton\").disabled(true).child(\n            v_flex()\n                .gap_1p5()\n                .overflow_hidden()\n                .child(Skeleton::new().h_5().w_48().max_w_full())\n                .child(Skeleton::new().secondary().h_3().w_64().max_w_full()),\n        )\n    }\n}\n\nimpl RenderOnce for Loading {\n    fn render(self, _window: &mut gpui::Window, _cx: &mut gpui::App) -> impl IntoElement {\n        v_flex()\n            .py_2p5()\n            .gap_3()\n            .child(LoadingItem)\n            .child(LoadingItem)\n            .child(LoadingItem)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/list/mod.rs",
    "content": "pub(crate) mod cache;\nmod delegate;\nmod list;\nmod list_item;\nmod loading;\nmod separator_item;\n\npub use delegate::*;\npub use list::*;\npub use list_item::*;\nuse schemars::JsonSchema;\npub use separator_item::*;\nuse serde::{Deserialize, Serialize};\n\n/// Settings for List.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct ListSettings {\n    /// Whether to use active highlight style on ListItem, default\n    pub active_highlight: bool,\n}\n\nimpl Default for ListSettings {\n    fn default() -> Self {\n        Self {\n            active_highlight: true,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/list/separator_item.rs",
    "content": "use gpui::{\n    AnyElement, ParentElement, RenderOnce, StyleRefinement,\n};\nuse smallvec::SmallVec;\n\nuse crate::{list::ListItem, Selectable, StyledExt};\n\npub struct ListSeparatorItem {\n    style: StyleRefinement,\n    children: SmallVec<[AnyElement; 2]>,\n}\n\nimpl ListSeparatorItem {\n    pub fn new() -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            children: SmallVec::new(),\n        }\n    }\n}\n\nimpl ParentElement for ListSeparatorItem {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Selectable for ListSeparatorItem {\n    fn selected(self, _: bool) -> Self {\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        false\n    }\n}\n\nimpl RenderOnce for ListSeparatorItem {\n    fn render(self, _: &mut gpui::Window, _: &mut gpui::App) -> impl gpui::IntoElement {\n        ListItem::new(\"separator\")\n            .refine_style(&self.style)\n            .children(self.children)\n            .disabled(true)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/menu/app_menu_bar.rs",
    "content": "use crate::{\n    Selectable, Sizable,\n    actions::{Cancel, SelectLeft, SelectRight},\n    button::{Button, ButtonVariants},\n    global_state::GlobalState,\n    h_flex,\n    menu::PopupMenu,\n};\nuse gpui::{\n    App, AppContext as _, ClickEvent, Context, DismissEvent, Entity, Focusable,\n    InteractiveElement as _, IntoElement, KeyBinding, MouseButton, OwnedMenu, ParentElement,\n    Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Window, anchored,\n    deferred, div, prelude::FluentBuilder, px,\n};\n\nconst CONTEXT: &str = \"AppMenuBar\";\npub fn init(cx: &mut App) {\n    cx.bind_keys([\n        KeyBinding::new(\"escape\", Cancel, Some(CONTEXT)),\n        KeyBinding::new(\"left\", SelectLeft, Some(CONTEXT)),\n        KeyBinding::new(\"right\", SelectRight, Some(CONTEXT)),\n    ]);\n}\n\n/// The application menu bar, for Windows and Linux.\npub struct AppMenuBar {\n    menus: Vec<Entity<AppMenu>>,\n    selected_index: Option<usize>,\n}\n\nimpl AppMenuBar {\n    /// Create a new app menu bar.\n    pub fn new(cx: &mut App) -> Entity<Self> {\n        cx.new(|cx| {\n            let mut this = Self {\n                selected_index: None,\n                menus: Vec::new(),\n            };\n            this.reload(cx);\n            this\n        })\n    }\n\n    /// Reload the menus from the app.\n    pub fn reload(&mut self, cx: &mut Context<Self>) {\n        let menu_bar = cx.entity();\n        let menus: Vec<OwnedMenu> = GlobalState::global(cx)\n            .app_menus()\n            .iter()\n            .cloned()\n            .collect();\n        self.menus = menus\n            .iter()\n            .enumerate()\n            .map(|(ix, menu)| AppMenu::new(ix, menu, menu_bar.clone(), cx))\n            .collect();\n        self.selected_index = None;\n        cx.notify();\n    }\n\n    fn on_move_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {\n        let Some(selected_index) = self.selected_index else {\n            return;\n        };\n\n        let new_ix = if selected_index == 0 {\n            self.menus.len().saturating_sub(1)\n        } else {\n            selected_index.saturating_sub(1)\n        };\n        self.set_selected_index(Some(new_ix), window, cx);\n    }\n\n    fn on_move_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {\n        let Some(selected_index) = self.selected_index else {\n            return;\n        };\n\n        let new_ix = if selected_index + 1 >= self.menus.len() {\n            0\n        } else {\n            selected_index + 1\n        };\n        self.set_selected_index(Some(new_ix), window, cx);\n    }\n\n    fn on_cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {\n        self.set_selected_index(None, window, cx);\n    }\n\n    fn set_selected_index(&mut self, ix: Option<usize>, _: &mut Window, cx: &mut Context<Self>) {\n        self.selected_index = ix;\n        cx.notify();\n    }\n\n    #[inline]\n    fn has_activated_menu(&self) -> bool {\n        self.selected_index.is_some()\n    }\n}\n\nimpl Render for AppMenuBar {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        h_flex()\n            .id(\"app-menu-bar\")\n            .key_context(CONTEXT)\n            .on_action(cx.listener(Self::on_move_left))\n            .on_action(cx.listener(Self::on_move_right))\n            .on_action(cx.listener(Self::on_cancel))\n            .size_full()\n            .gap_x_1()\n            .overflow_x_scroll()\n            .children(self.menus.clone())\n    }\n}\n\n/// A menu in the menu bar.\npub(super) struct AppMenu {\n    menu_bar: Entity<AppMenuBar>,\n    ix: usize,\n    name: SharedString,\n    menu: OwnedMenu,\n    popup_menu: Option<Entity<PopupMenu>>,\n\n    _subscription: Option<Subscription>,\n}\n\nimpl AppMenu {\n    pub(super) fn new(\n        ix: usize,\n        menu: &OwnedMenu,\n        menu_bar: Entity<AppMenuBar>,\n        cx: &mut App,\n    ) -> Entity<Self> {\n        let name = menu.name.clone();\n        cx.new(|_| Self {\n            ix,\n            menu_bar,\n            name,\n            menu: menu.clone(),\n            popup_menu: None,\n            _subscription: None,\n        })\n    }\n\n    fn build_popup_menu(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Entity<PopupMenu> {\n        let popup_menu = match self.popup_menu.as_ref() {\n            None => {\n                let items = self.menu.items.clone();\n                let popup_menu = PopupMenu::build(window, cx, |menu, window, cx| {\n                    menu.when_some(window.focused(cx), |this, handle| {\n                        this.action_context(handle)\n                    })\n                    .with_menu_items(items, window, cx)\n                });\n                popup_menu.read(cx).focus_handle(cx).focus(window, cx);\n                self._subscription =\n                    Some(cx.subscribe_in(&popup_menu, window, Self::handle_dismiss));\n                self.popup_menu = Some(popup_menu.clone());\n\n                popup_menu\n            }\n            Some(menu) => menu.clone(),\n        };\n\n        let focus_handle = popup_menu.read(cx).focus_handle(cx);\n        if !focus_handle.contains_focused(window, cx) {\n            focus_handle.focus(window, cx);\n        }\n\n        popup_menu\n    }\n\n    fn handle_dismiss(\n        &mut self,\n        _: &Entity<PopupMenu>,\n        _: &DismissEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self._subscription.take();\n        self.popup_menu.take();\n        self.menu_bar.update(cx, |state, cx| {\n            state.on_cancel(&Cancel, window, cx);\n        });\n    }\n\n    fn handle_trigger_click(\n        &mut self,\n        _: &ClickEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let is_selected = self.menu_bar.read(cx).selected_index == Some(self.ix);\n\n        _ = self.menu_bar.update(cx, |state, cx| {\n            let new_ix = if is_selected { None } else { Some(self.ix) };\n            state.set_selected_index(new_ix, window, cx);\n        });\n    }\n\n    fn handle_hover(&mut self, hovered: &bool, window: &mut Window, cx: &mut Context<Self>) {\n        if !*hovered {\n            return;\n        }\n\n        let has_activated_menu = self.menu_bar.read(cx).has_activated_menu();\n        if !has_activated_menu {\n            return;\n        }\n\n        _ = self.menu_bar.update(cx, |state, cx| {\n            state.set_selected_index(Some(self.ix), window, cx);\n        });\n    }\n}\n\nimpl Render for AppMenu {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let menu_bar = self.menu_bar.read(cx);\n        let is_selected = menu_bar.selected_index == Some(self.ix);\n\n        div()\n            .id(self.ix)\n            .relative()\n            .child(\n                Button::new(\"menu\")\n                    .small()\n                    .py_0p5()\n                    .compact()\n                    .ghost()\n                    .label(self.name.clone())\n                    .selected(is_selected)\n                    .on_mouse_down(MouseButton::Left, |_, window, cx| {\n                        // Stop propagation to avoid dragging the window.\n                        window.prevent_default();\n                        cx.stop_propagation();\n                    })\n                    .on_click(cx.listener(Self::handle_trigger_click)),\n            )\n            .on_hover(cx.listener(Self::handle_hover))\n            .when(is_selected, |this| {\n                this.child(deferred(\n                    anchored()\n                        .anchor(gpui::Corner::TopLeft)\n                        .snap_to_window_with_margin(px(8.))\n                        .child(\n                            div()\n                                .size_full()\n                                .occlude()\n                                .top_1()\n                                .child(self.build_popup_menu(window, cx)),\n                        ),\n                ))\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/menu/context_menu.rs",
    "content": "use std::{cell::RefCell, rc::Rc};\n\nuse gpui::{\n    AnyElement, App, Context, Corner, DismissEvent, Element, ElementId, Entity, Focusable,\n    GlobalElementId, Hitbox, HitboxBehavior, InspectorElementId, InteractiveElement, IntoElement,\n    MouseButton, MouseDownEvent, ParentElement, Pixels, Point, StyleRefinement, Styled,\n    Subscription, Window, anchored, deferred, div, prelude::FluentBuilder, px,\n};\n\nuse crate::menu::PopupMenu;\n\n/// A extension trait for adding a context menu to an element.\npub trait ContextMenuExt: InteractiveElement + ParentElement + Styled {\n    /// Add a context menu to the element.\n    ///\n    /// This will changed the element to be `relative` positioned, and add a child `ContextMenu` element.\n    /// Because the `ContextMenu` element is positioned `absolute`, it will not affect the layout of the parent element.\n    fn context_menu(\n        mut self,\n        f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,\n    ) -> ContextMenu<Self>\n    where\n        Self: Sized,\n    {\n        // Generate a unique ID based on the element's memory address to ensure\n        // each context menu has its own state and doesn't share with others\n        let id = self\n            .interactivity()\n            .element_id\n            .clone()\n            .map(|id| format!(\"context-menu-{:?}\", id))\n            .unwrap_or_else(|| format!(\"context-menu-{:p}\", &self as *const _));\n        ContextMenu::new(id, self).menu(f)\n    }\n}\n\nimpl<E: InteractiveElement + ParentElement + Styled> ContextMenuExt for E {}\n\n/// A context menu that can be shown on right-click.\npub struct ContextMenu<E: ParentElement + Styled + Sized> {\n    id: ElementId,\n    element: Option<E>,\n    menu: Option<Rc<dyn Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu>>,\n    // This is not in use, just for style refinement forwarding.\n    _ignore_style: StyleRefinement,\n    anchor: Corner,\n}\n\nimpl<E: ParentElement + Styled> ContextMenu<E> {\n    /// Create a new context menu with the given ID.\n    pub fn new(id: impl Into<ElementId>, element: E) -> Self {\n        Self {\n            id: id.into(),\n            element: Some(element),\n            menu: None,\n            anchor: Corner::TopLeft,\n            _ignore_style: StyleRefinement::default(),\n        }\n    }\n\n    /// Build the context menu using the given builder function.\n    #[must_use]\n    fn menu<F>(mut self, builder: F) -> Self\n    where\n        F: Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,\n    {\n        self.menu = Some(Rc::new(builder));\n        self\n    }\n\n    fn with_element_state<R>(\n        &mut self,\n        id: &GlobalElementId,\n        window: &mut Window,\n        cx: &mut App,\n        f: impl FnOnce(&mut Self, &mut ContextMenuState, &mut Window, &mut App) -> R,\n    ) -> R {\n        window.with_optional_element_state::<ContextMenuState, _>(\n            Some(id),\n            |element_state, window| {\n                let mut element_state = element_state.unwrap().unwrap_or_default();\n                let result = f(self, &mut element_state, window, cx);\n                (result, Some(element_state))\n            },\n        )\n    }\n}\n\nimpl<E: ParentElement + Styled> ParentElement for ContextMenu<E> {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        if let Some(element) = &mut self.element {\n            element.extend(elements);\n        }\n    }\n}\n\nimpl<E: ParentElement + Styled> Styled for ContextMenu<E> {\n    fn style(&mut self) -> &mut StyleRefinement {\n        if let Some(element) = &mut self.element {\n            element.style()\n        } else {\n            &mut self._ignore_style\n        }\n    }\n}\n\nimpl<E: ParentElement + Styled + IntoElement + 'static> IntoElement for ContextMenu<E> {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\nstruct ContextMenuSharedState {\n    menu_view: Option<Entity<PopupMenu>>,\n    open: bool,\n    position: Point<Pixels>,\n    _subscription: Option<Subscription>,\n}\n\npub struct ContextMenuState {\n    element: Option<AnyElement>,\n    shared_state: Rc<RefCell<ContextMenuSharedState>>,\n}\n\nimpl Default for ContextMenuState {\n    fn default() -> Self {\n        Self {\n            element: None,\n            shared_state: Rc::new(RefCell::new(ContextMenuSharedState {\n                menu_view: None,\n                open: false,\n                position: Default::default(),\n                _subscription: None,\n            })),\n        }\n    }\n}\n\nimpl<E: ParentElement + Styled + IntoElement + 'static> Element for ContextMenu<E> {\n    type RequestLayoutState = ContextMenuState;\n    type PrepaintState = Hitbox;\n\n    fn id(&self) -> Option<ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        id: Option<&gpui::GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (gpui::LayoutId, Self::RequestLayoutState) {\n        let anchor = self.anchor;\n\n        self.with_element_state(\n            id.unwrap(),\n            window,\n            cx,\n            |this, state: &mut ContextMenuState, window, cx| {\n                let (position, open) = {\n                    let shared_state = state.shared_state.borrow();\n                    (shared_state.position, shared_state.open)\n                };\n                let menu_view = state.shared_state.borrow().menu_view.clone();\n                let mut menu_element = None;\n                if open {\n                    let has_menu_item = menu_view\n                        .as_ref()\n                        .map(|menu| !menu.read(cx).is_empty())\n                        .unwrap_or(false);\n\n                    if has_menu_item {\n                        menu_element = Some(\n                            deferred(\n                                anchored().child(\n                                    div()\n                                        .w(window.bounds().size.width)\n                                        .h(window.bounds().size.height)\n                                        .on_scroll_wheel(|_, _, cx| {\n                                            cx.stop_propagation();\n                                        })\n                                        .child(\n                                            anchored()\n                                                .position(position)\n                                                .snap_to_window_with_margin(px(8.))\n                                                .anchor(anchor)\n                                                .when_some(menu_view, |this, menu| {\n                                                    // Focus the menu, so that can be handle the action.\n                                                    if !menu\n                                                        .focus_handle(cx)\n                                                        .contains_focused(window, cx)\n                                                    {\n                                                        menu.focus_handle(cx).focus(window, cx);\n                                                    }\n\n                                                    this.child(menu.clone())\n                                                }),\n                                        ),\n                                ),\n                            )\n                            .with_priority(1)\n                            .into_any(),\n                        );\n                    }\n                }\n\n                let mut element = this\n                    .element\n                    .take()\n                    .expect(\"Element should exists.\")\n                    .children(menu_element)\n                    .into_any_element();\n\n                let layout_id = element.request_layout(window, cx);\n\n                (\n                    layout_id,\n                    ContextMenuState {\n                        element: Some(element),\n                        ..Default::default()\n                    },\n                )\n            },\n        )\n    }\n\n    fn prepaint(\n        &mut self,\n        _: Option<&gpui::GlobalElementId>,\n        _: Option<&InspectorElementId>,\n        bounds: gpui::Bounds<gpui::Pixels>,\n        request_layout: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::PrepaintState {\n        if let Some(element) = &mut request_layout.element {\n            element.prepaint(window, cx);\n        }\n        window.insert_hitbox(bounds, HitboxBehavior::Normal)\n    }\n\n    fn paint(\n        &mut self,\n        id: Option<&gpui::GlobalElementId>,\n        _: Option<&InspectorElementId>,\n        _: gpui::Bounds<gpui::Pixels>,\n        request_layout: &mut Self::RequestLayoutState,\n        hitbox: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        if let Some(element) = &mut request_layout.element {\n            element.paint(window, cx);\n        }\n\n        // Take the builder before setting up element state to avoid borrow issues\n        let builder = self.menu.clone();\n\n        self.with_element_state(\n            id.unwrap(),\n            window,\n            cx,\n            |_view, state: &mut ContextMenuState, window, _| {\n                let shared_state = state.shared_state.clone();\n\n                let hitbox = hitbox.clone();\n                // When right mouse click, to build content menu, and show it at the mouse position.\n                window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {\n                    if phase.bubble()\n                        && event.button == MouseButton::Right\n                        && hitbox.is_hovered(window)\n                    {\n                        {\n                            let mut shared_state = shared_state.borrow_mut();\n                            // Clear any existing menu view to allow immediate replacement\n                            // Set the new position and open the menu\n                            shared_state.menu_view = None;\n                            shared_state._subscription = None;\n                            shared_state.position = event.position;\n                            shared_state.open = true;\n                        }\n\n                        // Use defer to build the menu in the next frame, avoiding race conditions\n                        window.defer(cx, {\n                            let shared_state = shared_state.clone();\n                            let builder = builder.clone();\n                            move |window, cx| {\n                                let menu = PopupMenu::build(window, cx, move |menu, window, cx| {\n                                    let Some(build) = &builder else {\n                                        return menu;\n                                    };\n                                    build(menu, window, cx)\n                                });\n\n                                // Set up the subscription for dismiss handling\n                                let _subscription = window.subscribe(&menu, cx, {\n                                    let shared_state = shared_state.clone();\n                                    move |_, _: &DismissEvent, window, _cx| {\n                                        shared_state.borrow_mut().open = false;\n                                        window.refresh();\n                                    }\n                                });\n\n                                // Update the shared state with the built menu and subscription\n                                {\n                                    let mut state = shared_state.borrow_mut();\n                                    state.menu_view = Some(menu.clone());\n                                    state._subscription = Some(_subscription);\n                                    window.refresh();\n                                }\n                            }\n                        });\n                    }\n                });\n            },\n        );\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/menu/dropdown_menu.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{\n    Context, Corner, DismissEvent, ElementId, Entity, Focusable, InteractiveElement, IntoElement,\n    RenderOnce, SharedString, StyleRefinement, Styled, Window,\n};\n\nuse crate::{Selectable, button::Button, menu::PopupMenu, popover::Popover};\n\n/// A dropdown menu trait for buttons and other interactive elements\npub trait DropdownMenu: Styled + Selectable + InteractiveElement + IntoElement + 'static {\n    /// Create a dropdown menu with the given items, anchored to the TopLeft corner\n    fn dropdown_menu(\n        self,\n        f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,\n    ) -> DropdownMenuPopover<Self> {\n        self.dropdown_menu_with_anchor(Corner::TopLeft, f)\n    }\n\n    /// Create a dropdown menu with the given items, anchored to the given corner\n    fn dropdown_menu_with_anchor(\n        mut self,\n        anchor: impl Into<Corner>,\n        f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,\n    ) -> DropdownMenuPopover<Self> {\n        let style = self.style().clone();\n        let id = self.interactivity().element_id.clone();\n\n        DropdownMenuPopover::new(id.unwrap_or(0.into()), anchor, self, f).trigger_style(style)\n    }\n}\n\nimpl DropdownMenu for Button {}\n\n#[derive(IntoElement)]\npub struct DropdownMenuPopover<T: Selectable + IntoElement + 'static> {\n    id: ElementId,\n    style: StyleRefinement,\n    anchor: Corner,\n    trigger: T,\n    builder: Rc<dyn Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu>,\n}\n\nimpl<T> DropdownMenuPopover<T>\nwhere\n    T: Selectable + IntoElement + 'static,\n{\n    fn new(\n        id: ElementId,\n        anchor: impl Into<Corner>,\n        trigger: T,\n        builder: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,\n    ) -> Self {\n        Self {\n            id: SharedString::from(format!(\"dropdown-menu:{:?}\", id)).into(),\n            style: StyleRefinement::default(),\n            anchor: anchor.into(),\n            trigger,\n            builder: Rc::new(builder),\n        }\n    }\n\n    /// Set the anchor corner for the dropdown menu popover.\n    pub fn anchor(mut self, anchor: impl Into<Corner>) -> Self {\n        self.anchor = anchor.into();\n        self\n    }\n\n    /// Set the style refinement for the dropdown menu trigger.\n    fn trigger_style(mut self, style: StyleRefinement) -> Self {\n        self.style = style;\n        self\n    }\n}\n\n#[derive(Default)]\nstruct DropdownMenuState {\n    menu: Option<Entity<PopupMenu>>,\n}\n\nimpl<T> RenderOnce for DropdownMenuPopover<T>\nwhere\n    T: Selectable + IntoElement + 'static,\n{\n    fn render(self, window: &mut Window, cx: &mut gpui::App) -> impl IntoElement {\n        let builder = self.builder.clone();\n        let menu_state =\n            window.use_keyed_state(self.id.clone(), cx, |_, _| DropdownMenuState::default());\n\n        Popover::new(SharedString::from(format!(\"popover:{}\", self.id)))\n            .appearance(false)\n            .overlay_closable(false)\n            .trigger(self.trigger)\n            .trigger_style(self.style)\n            .anchor(self.anchor)\n            .content(move |_, window, cx| {\n                // Here is special logic to only create the PopupMenu once and reuse it.\n                // Because this `content` will called in every time render, so we need to store the menu\n                // in state to avoid recreating at every render.\n                //\n                // And we also need to rebuild the menu when it is dismissed, to rebuild menu items\n                // dynamically for support `dropdown_menu` method, so we listen for DismissEvent below.\n                let menu = match menu_state.read(cx).menu.clone() {\n                    Some(menu) => menu,\n                    None => {\n                        let builder = builder.clone();\n                        let menu = PopupMenu::build(window, cx, move |menu, window, cx| {\n                            builder(menu, window, cx)\n                        });\n                        menu_state.update(cx, |state, _| {\n                            state.menu = Some(menu.clone());\n                        });\n                        menu.focus_handle(cx).focus(window, cx);\n\n                        // Listen for dismiss events from the PopupMenu to close the popover.\n                        let popover_state = cx.entity();\n                        window\n                            .subscribe(&menu, cx, {\n                                let menu_state = menu_state.clone();\n                                move |_, _: &DismissEvent, window, cx| {\n                                    popover_state.update(cx, |state, cx| {\n                                        state.dismiss(window, cx);\n                                    });\n                                    menu_state.update(cx, |state, _| {\n                                        state.menu = None;\n                                    });\n                                }\n                            })\n                            .detach();\n\n                        menu.clone()\n                    }\n                };\n\n                menu.clone()\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/menu/menu_item.rs",
    "content": "use crate::{h_flex, ActiveTheme, Disableable, StyledExt};\nuse gpui::{\n    prelude::FluentBuilder as _, AnyElement, App, ClickEvent, ElementId, InteractiveElement,\n    IntoElement, MouseButton, ParentElement, RenderOnce, SharedString,\n    StatefulInteractiveElement as _, StyleRefinement, Styled, Window,\n};\nuse smallvec::SmallVec;\n\n#[derive(IntoElement)]\npub(crate) struct MenuItemElement {\n    id: ElementId,\n    group_name: SharedString,\n    style: StyleRefinement,\n    disabled: bool,\n    selected: bool,\n    on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,\n    on_hover: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,\n    children: SmallVec<[AnyElement; 2]>,\n}\n\nimpl MenuItemElement {\n    /// Create a new MenuItem with the given ID and group name.\n    pub(crate) fn new(id: impl Into<ElementId>, group_name: impl Into<SharedString>) -> Self {\n        let id: ElementId = id.into();\n        Self {\n            id: id.clone(),\n            group_name: group_name.into(),\n            style: StyleRefinement::default(),\n            disabled: false,\n            selected: false,\n            on_click: None,\n            on_hover: None,\n            children: SmallVec::new(),\n        }\n    }\n\n    /// Set ListItem as the selected item style.\n    pub(crate) fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    /// Set the disabled state of the MenuItem.\n    pub(crate) fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    /// Set a handler for when the MenuItem is clicked.\n    pub(crate) fn on_click(\n        mut self,\n        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_click = Some(Box::new(handler));\n        self\n    }\n\n    /// Set a handler for when the mouse enters the MenuItem.\n    #[allow(unused)]\n    pub fn on_hover(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {\n        self.on_hover = Some(Box::new(handler));\n        self\n    }\n}\n\nimpl Disableable for MenuItemElement {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl Styled for MenuItemElement {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl ParentElement for MenuItemElement {\n    fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl RenderOnce for MenuItemElement {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        h_flex()\n            .id(self.id)\n            .group(&self.group_name)\n            .gap_x_1()\n            .py_1()\n            .px_2()\n            .text_base()\n            .text_color(cx.theme().foreground)\n            .relative()\n            .items_center()\n            .justify_between()\n            .refine_style(&self.style)\n            .when_some(self.on_hover, |this, on_hover| {\n                this.on_hover(move |hovered, window, cx| (on_hover)(hovered, window, cx))\n            })\n            .when(!self.disabled, |this| {\n                this.group_hover(self.group_name, |this| {\n                    this.bg(cx.theme().accent)\n                        .text_color(cx.theme().accent_foreground)\n                })\n                .when(self.selected, |this| {\n                    this.bg(cx.theme().accent)\n                        .text_color(cx.theme().accent_foreground)\n                })\n                .when_some(self.on_click, |this, on_click| {\n                    this.on_mouse_down(MouseButton::Left, move |_, _, cx| {\n                        cx.stop_propagation();\n                    })\n                    .on_click(on_click)\n                })\n            })\n            .when(self.disabled, |this| {\n                this.text_color(cx.theme().muted_foreground)\n            })\n            .children(self.children)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/menu/mod.rs",
    "content": "use gpui::App;\n\nmod app_menu_bar;\nmod context_menu;\nmod dropdown_menu;\nmod menu_item;\nmod popup_menu;\n\npub use app_menu_bar::AppMenuBar;\npub use context_menu::{ContextMenu, ContextMenuExt, ContextMenuState};\npub use dropdown_menu::DropdownMenu;\npub use popup_menu::{PopupMenu, PopupMenuItem};\n\npub(crate) fn init(cx: &mut App) {\n    app_menu_bar::init(cx);\n    popup_menu::init(cx);\n}\n"
  },
  {
    "path": "crates/ui/src/menu/popup_menu.rs",
    "content": "use crate::actions::{Cancel, Confirm, SelectDown, SelectUp};\nuse crate::actions::{SelectLeft, SelectRight};\nuse crate::menu::menu_item::MenuItemElement;\nuse crate::scroll::ScrollableElement;\nuse crate::{ActiveTheme, ElementExt, Icon, IconName, Sizable as _, h_flex, v_flex};\nuse crate::{Side, Size, StyledExt, kbd::Kbd};\nuse gpui::{\n    Action, AnyElement, App, AppContext, Bounds, Context, Corner, DismissEvent, Edges, Entity,\n    EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding,\n    ParentElement, Pixels, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled,\n    WeakEntity, Window, anchored, div, prelude::FluentBuilder, px, rems,\n};\nuse gpui::{ClickEvent, Half, MouseDownEvent, OwnedMenuItem, Point, Subscription};\nuse std::rc::Rc;\n\nconst CONTEXT: &str = \"PopupMenu\";\n\npub fn init(cx: &mut App) {\n    cx.bind_keys([\n        KeyBinding::new(\"enter\", Confirm { secondary: false }, Some(CONTEXT)),\n        KeyBinding::new(\"escape\", Cancel, Some(CONTEXT)),\n        KeyBinding::new(\"up\", SelectUp, Some(CONTEXT)),\n        KeyBinding::new(\"down\", SelectDown, Some(CONTEXT)),\n        KeyBinding::new(\"left\", SelectLeft, Some(CONTEXT)),\n        KeyBinding::new(\"right\", SelectRight, Some(CONTEXT)),\n    ]);\n}\n\n/// An menu item in a popup menu.\npub enum PopupMenuItem {\n    /// A menu separator item.\n    Separator,\n    /// A non-interactive label item.\n    Label(SharedString),\n    /// A standard menu item.\n    Item {\n        icon: Option<Icon>,\n        label: SharedString,\n        disabled: bool,\n        checked: bool,\n        is_link: bool,\n        action: Option<Box<dyn Action>>,\n        // For link item\n        handler: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,\n    },\n    /// A menu item with custom element render.\n    ElementItem {\n        icon: Option<Icon>,\n        disabled: bool,\n        checked: bool,\n        action: Option<Box<dyn Action>>,\n        render: Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>,\n        handler: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,\n    },\n    /// A submenu item that opens another popup menu.\n    ///\n    /// NOTE: This is only supported when the parent menu is not `scrollable`.\n    Submenu {\n        icon: Option<Icon>,\n        label: SharedString,\n        disabled: bool,\n        menu: Entity<PopupMenu>,\n    },\n}\n\nimpl FluentBuilder for PopupMenuItem {}\nimpl PopupMenuItem {\n    /// Create a new menu item with the given label.\n    #[inline]\n    pub fn new(label: impl Into<SharedString>) -> Self {\n        PopupMenuItem::Item {\n            icon: None,\n            label: label.into(),\n            disabled: false,\n            checked: false,\n            action: None,\n            is_link: false,\n            handler: None,\n        }\n    }\n\n    /// Create a new menu item with custom element render.\n    #[inline]\n    pub fn element<F, E>(builder: F) -> Self\n    where\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n        E: IntoElement,\n    {\n        PopupMenuItem::ElementItem {\n            icon: None,\n            disabled: false,\n            checked: false,\n            action: None,\n            render: Box::new(move |window, cx| builder(window, cx).into_any_element()),\n            handler: None,\n        }\n    }\n\n    /// Create a new submenu item that opens another popup menu.\n    #[inline]\n    pub fn submenu(label: impl Into<SharedString>, menu: Entity<PopupMenu>) -> Self {\n        PopupMenuItem::Submenu {\n            icon: None,\n            label: label.into(),\n            disabled: false,\n            menu,\n        }\n    }\n\n    /// Create a separator menu item.\n    #[inline]\n    pub fn separator() -> Self {\n        PopupMenuItem::Separator\n    }\n\n    /// Creates a label menu item.\n    #[inline]\n    pub fn label(label: impl Into<SharedString>) -> Self {\n        PopupMenuItem::Label(label.into())\n    }\n\n    /// Set the icon for the menu item.\n    ///\n    /// Only works for [`PopupMenuItem::Item`], [`PopupMenuItem::ElementItem`] and [`PopupMenuItem::Submenu`].\n    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {\n        match &mut self {\n            PopupMenuItem::Item { icon: i, .. } => {\n                *i = Some(icon.into());\n            }\n            PopupMenuItem::ElementItem { icon: i, .. } => {\n                *i = Some(icon.into());\n            }\n            PopupMenuItem::Submenu { icon: i, .. } => {\n                *i = Some(icon.into());\n            }\n            _ => {}\n        }\n        self\n    }\n\n    /// Set the action for the menu item.\n    ///\n    /// Only works for [`PopupMenuItem::Item`] and [`PopupMenuItem::ElementItem`].\n    pub fn action(mut self, action: Box<dyn Action>) -> Self {\n        match &mut self {\n            PopupMenuItem::Item { action: a, .. } => {\n                *a = Some(action);\n            }\n            PopupMenuItem::ElementItem { action: a, .. } => {\n                *a = Some(action);\n            }\n            _ => {}\n        }\n        self\n    }\n\n    /// Set the disabled state for the menu item.\n    ///\n    /// Only works for [`PopupMenuItem::Item`], [`PopupMenuItem::ElementItem`] and [`PopupMenuItem::Submenu`].\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        match &mut self {\n            PopupMenuItem::Item { disabled: d, .. } => {\n                *d = disabled;\n            }\n            PopupMenuItem::ElementItem { disabled: d, .. } => {\n                *d = disabled;\n            }\n            PopupMenuItem::Submenu { disabled: d, .. } => {\n                *d = disabled;\n            }\n            _ => {}\n        }\n        self\n    }\n\n    /// Set checked state for the menu item.\n    ///\n    /// NOTE: If `check_side` is [`Side::Left`], the icon will replace with a check icon.\n    pub fn checked(mut self, checked: bool) -> Self {\n        match &mut self {\n            PopupMenuItem::Item { checked: c, .. } => {\n                *c = checked;\n            }\n            PopupMenuItem::ElementItem { checked: c, .. } => {\n                *c = checked;\n            }\n            _ => {}\n        }\n        self\n    }\n\n    /// Add a click handler for the menu item.\n    ///\n    /// Only works for [`PopupMenuItem::Item`] and [`PopupMenuItem::ElementItem`].\n    pub fn on_click<F>(mut self, handler: F) -> Self\n    where\n        F: Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    {\n        match &mut self {\n            PopupMenuItem::Item { handler: h, .. } => {\n                *h = Some(Rc::new(handler));\n            }\n            PopupMenuItem::ElementItem { handler: h, .. } => {\n                *h = Some(Rc::new(handler));\n            }\n            _ => {}\n        }\n        self\n    }\n\n    /// Create a link menu item.\n    #[inline]\n    pub fn link(label: impl Into<SharedString>, href: impl Into<String>) -> Self {\n        let href = href.into();\n        PopupMenuItem::Item {\n            icon: None,\n            label: label.into(),\n            disabled: false,\n            checked: false,\n            action: None,\n            is_link: true,\n            handler: Some(Rc::new(move |_, _, cx| cx.open_url(&href))),\n        }\n    }\n\n    #[inline]\n    fn is_clickable(&self) -> bool {\n        !matches!(self, PopupMenuItem::Separator)\n            && matches!(\n                self,\n                PopupMenuItem::Item {\n                    disabled: false,\n                    ..\n                } | PopupMenuItem::ElementItem {\n                    disabled: false,\n                    ..\n                } | PopupMenuItem::Submenu {\n                    disabled: false,\n                    ..\n                }\n            )\n    }\n\n    #[inline]\n    fn is_separator(&self) -> bool {\n        matches!(self, PopupMenuItem::Separator)\n    }\n\n    fn has_left_icon(&self, check_side: Side) -> bool {\n        match self {\n            PopupMenuItem::Item { icon, checked, .. } => {\n                icon.is_some() || (check_side.is_left() && *checked)\n            }\n            PopupMenuItem::ElementItem { icon, checked, .. } => {\n                icon.is_some() || (check_side.is_left() && *checked)\n            }\n            PopupMenuItem::Submenu { icon, .. } => icon.is_some(),\n            _ => false,\n        }\n    }\n\n    #[inline]\n    fn is_checked(&self) -> bool {\n        match self {\n            PopupMenuItem::Item { checked, .. } => *checked,\n            PopupMenuItem::ElementItem { checked, .. } => *checked,\n            _ => false,\n        }\n    }\n}\n\npub struct PopupMenu {\n    pub(crate) focus_handle: FocusHandle,\n    pub(crate) menu_items: Vec<PopupMenuItem>,\n    /// The focus handle of Entity to handle actions.\n    pub(crate) action_context: Option<FocusHandle>,\n    selected_index: Option<usize>,\n    min_width: Option<Pixels>,\n    max_width: Option<Pixels>,\n    max_height: Option<Pixels>,\n    bounds: Bounds<Pixels>,\n    size: Size,\n    check_side: Side,\n\n    /// The parent menu of this menu, if this is a submenu\n    parent_menu: Option<WeakEntity<Self>>,\n    scrollable: bool,\n    external_link_icon: bool,\n    scroll_handle: ScrollHandle,\n    // This will update on render\n    submenu_anchor: (Corner, Pixels),\n\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl PopupMenu {\n    pub(crate) fn new(cx: &mut App) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            action_context: None,\n            parent_menu: None,\n            menu_items: Vec::new(),\n            selected_index: None,\n            min_width: None,\n            max_width: None,\n            max_height: None,\n            check_side: Side::Left,\n            bounds: Bounds::default(),\n            scrollable: false,\n            scroll_handle: ScrollHandle::default(),\n            external_link_icon: true,\n            size: Size::default(),\n            submenu_anchor: (Corner::TopLeft, Pixels::ZERO),\n            _subscriptions: vec![],\n        }\n    }\n\n    pub fn build(\n        window: &mut Window,\n        cx: &mut App,\n        f: impl FnOnce(Self, &mut Window, &mut Context<PopupMenu>) -> Self,\n    ) -> Entity<Self> {\n        cx.new(|cx| f(Self::new(cx), window, cx))\n    }\n\n    /// Set the focus handle of Entity to handle actions.\n    ///\n    /// When the menu is dismissed or before an action is triggered, the focus will be returned to this handle.\n    ///\n    /// Then the action will be dispatched to this handle.\n    pub fn action_context(mut self, handle: FocusHandle) -> Self {\n        self.action_context = Some(handle);\n        self\n    }\n\n    /// Set min width of the popup menu, default is 120px\n    pub fn min_w(mut self, width: impl Into<Pixels>) -> Self {\n        self.min_width = Some(width.into());\n        self\n    }\n\n    /// Set max width of the popup menu, default is 500px\n    pub fn max_w(mut self, width: impl Into<Pixels>) -> Self {\n        self.max_width = Some(width.into());\n        self\n    }\n\n    /// Set max height of the popup menu, default is half of the window height\n    pub fn max_h(mut self, height: impl Into<Pixels>) -> Self {\n        self.max_height = Some(height.into());\n        self\n    }\n\n    /// Set the menu to be scrollable to show vertical scrollbar.\n    ///\n    /// NOTE: If this is true, the sub-menus will cannot be support.\n    pub fn scrollable(mut self, scrollable: bool) -> Self {\n        self.scrollable = scrollable;\n        self\n    }\n\n    /// Set the side to show check icon, default is `Side::Left`.\n    pub fn check_side(mut self, side: Side) -> Self {\n        self.check_side = side;\n        self\n    }\n\n    /// Set the menu to show external link icon, default is true.\n    pub fn external_link_icon(mut self, visible: bool) -> Self {\n        self.external_link_icon = visible;\n        self\n    }\n\n    /// Add Menu Item\n    pub fn menu(self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {\n        self.menu_with_disabled(label, action, false)\n    }\n\n    /// Add Menu Item with enable state\n    pub fn menu_with_enable(\n        mut self,\n        label: impl Into<SharedString>,\n        action: Box<dyn Action>,\n        enable: bool,\n    ) -> Self {\n        self.add_menu_item(label, None, action, !enable, false);\n        self\n    }\n\n    /// Add Menu Item with disabled state\n    pub fn menu_with_disabled(\n        mut self,\n        label: impl Into<SharedString>,\n        action: Box<dyn Action>,\n        disabled: bool,\n    ) -> Self {\n        self.add_menu_item(label, None, action, disabled, false);\n        self\n    }\n\n    /// Add label\n    pub fn label(mut self, label: impl Into<SharedString>) -> Self {\n        self.menu_items.push(PopupMenuItem::label(label.into()));\n        self\n    }\n\n    /// Add Menu to open link\n    pub fn link(self, label: impl Into<SharedString>, href: impl Into<String>) -> Self {\n        self.link_with_disabled(label, href, false)\n    }\n\n    /// Add Menu to open link with disabled state\n    pub fn link_with_disabled(\n        mut self,\n        label: impl Into<SharedString>,\n        href: impl Into<String>,\n        disabled: bool,\n    ) -> Self {\n        let href = href.into();\n        self.menu_items\n            .push(PopupMenuItem::link(label, href).disabled(disabled));\n        self\n    }\n\n    /// Add Menu to open link\n    pub fn link_with_icon(\n        self,\n        label: impl Into<SharedString>,\n        icon: impl Into<Icon>,\n        href: impl Into<String>,\n    ) -> Self {\n        self.link_with_icon_and_disabled(label, icon, href, false)\n    }\n\n    /// Add Menu to open link with icon and disabled state\n    fn link_with_icon_and_disabled(\n        mut self,\n        label: impl Into<SharedString>,\n        icon: impl Into<Icon>,\n        href: impl Into<String>,\n        disabled: bool,\n    ) -> Self {\n        let href = href.into();\n        self.menu_items.push(\n            PopupMenuItem::link(label, href)\n                .icon(icon)\n                .disabled(disabled),\n        );\n        self\n    }\n\n    /// Add Menu Item with Icon.\n    pub fn menu_with_icon(\n        self,\n        label: impl Into<SharedString>,\n        icon: impl Into<Icon>,\n        action: Box<dyn Action>,\n    ) -> Self {\n        self.menu_with_icon_and_disabled(label, icon, action, false)\n    }\n\n    /// Add Menu Item with Icon and disabled state\n    pub fn menu_with_icon_and_disabled(\n        mut self,\n        label: impl Into<SharedString>,\n        icon: impl Into<Icon>,\n        action: Box<dyn Action>,\n        disabled: bool,\n    ) -> Self {\n        self.add_menu_item(label, Some(icon.into()), action, disabled, false);\n        self\n    }\n\n    /// Add Menu Item with check icon\n    pub fn menu_with_check(\n        self,\n        label: impl Into<SharedString>,\n        checked: bool,\n        action: Box<dyn Action>,\n    ) -> Self {\n        self.menu_with_check_and_disabled(label, checked, action, false)\n    }\n\n    /// Add Menu Item with check icon and disabled state\n    pub fn menu_with_check_and_disabled(\n        mut self,\n        label: impl Into<SharedString>,\n        checked: bool,\n        action: Box<dyn Action>,\n        disabled: bool,\n    ) -> Self {\n        self.add_menu_item(label, None, action, disabled, checked);\n        self\n    }\n\n    /// Add Menu Item with custom element render.\n    pub fn menu_element<F, E>(self, action: Box<dyn Action>, builder: F) -> Self\n    where\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n        E: IntoElement,\n    {\n        self.menu_element_with_check(false, action, builder)\n    }\n\n    /// Add Menu Item with custom element render with disabled state.\n    pub fn menu_element_with_disabled<F, E>(\n        self,\n        action: Box<dyn Action>,\n        disabled: bool,\n        builder: F,\n    ) -> Self\n    where\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n        E: IntoElement,\n    {\n        self.menu_element_with_check_and_disabled(false, action, disabled, builder)\n    }\n\n    /// Add Menu Item with custom element render with icon.\n    pub fn menu_element_with_icon<F, E>(\n        self,\n        icon: impl Into<Icon>,\n        action: Box<dyn Action>,\n        builder: F,\n    ) -> Self\n    where\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n        E: IntoElement,\n    {\n        self.menu_element_with_icon_and_disabled(icon, action, false, builder)\n    }\n\n    /// Add Menu Item with custom element render with check state\n    pub fn menu_element_with_check<F, E>(\n        self,\n        checked: bool,\n        action: Box<dyn Action>,\n        builder: F,\n    ) -> Self\n    where\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n        E: IntoElement,\n    {\n        self.menu_element_with_check_and_disabled(checked, action, false, builder)\n    }\n\n    /// Add Menu Item with custom element render with icon and disabled state\n    fn menu_element_with_icon_and_disabled<F, E>(\n        mut self,\n        icon: impl Into<Icon>,\n        action: Box<dyn Action>,\n        disabled: bool,\n        builder: F,\n    ) -> Self\n    where\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n        E: IntoElement,\n    {\n        self.menu_items.push(\n            PopupMenuItem::element(builder)\n                .action(action)\n                .icon(icon)\n                .disabled(disabled),\n        );\n        self\n    }\n\n    /// Add Menu Item with custom element render with check state and disabled state\n    fn menu_element_with_check_and_disabled<F, E>(\n        mut self,\n        checked: bool,\n        action: Box<dyn Action>,\n        disabled: bool,\n        builder: F,\n    ) -> Self\n    where\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n        E: IntoElement,\n    {\n        self.menu_items.push(\n            PopupMenuItem::element(builder)\n                .action(action)\n                .checked(checked)\n                .disabled(disabled),\n        );\n        self\n    }\n\n    /// Add a separator Menu Item\n    pub fn separator(mut self) -> Self {\n        if self.menu_items.is_empty() {\n            return self;\n        }\n\n        if let Some(PopupMenuItem::Separator) = self.menu_items.last() {\n            return self;\n        }\n\n        self.menu_items.push(PopupMenuItem::separator());\n        self\n    }\n\n    /// Add a Submenu\n    pub fn submenu(\n        self,\n        label: impl Into<SharedString>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n        f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,\n    ) -> Self {\n        self.submenu_with_icon(None, label, window, cx, f)\n    }\n\n    /// Add a Submenu item with icon\n    pub fn submenu_with_icon(\n        mut self,\n        icon: Option<Icon>,\n        label: impl Into<SharedString>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n        f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,\n    ) -> Self {\n        let submenu = PopupMenu::build(window, cx, f);\n        let parent_menu = cx.entity().downgrade();\n        submenu.update(cx, |view, _| {\n            view.parent_menu = Some(parent_menu);\n        });\n\n        self.menu_items.push(\n            PopupMenuItem::submenu(label, submenu).when_some(icon, |this, icon| this.icon(icon)),\n        );\n        self\n    }\n\n    /// Add menu item.\n    pub fn item(mut self, item: impl Into<PopupMenuItem>) -> Self {\n        let item: PopupMenuItem = item.into();\n        self.menu_items.push(item);\n        self\n    }\n\n    /// Use small size, the menu item will have smaller height.\n    pub(crate) fn small(mut self) -> Self {\n        self.size = Size::Small;\n        self\n    }\n\n    fn add_menu_item(\n        &mut self,\n        label: impl Into<SharedString>,\n        icon: Option<Icon>,\n        action: Box<dyn Action>,\n        disabled: bool,\n        checked: bool,\n    ) -> &mut Self {\n        self.menu_items.push(\n            PopupMenuItem::new(label)\n                .when_some(icon, |item, icon| item.icon(icon))\n                .disabled(disabled)\n                .checked(checked)\n                .action(action),\n        );\n        self\n    }\n\n    pub(super) fn with_menu_items<I>(\n        mut self,\n        items: impl IntoIterator<Item = I>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Self\n    where\n        I: Into<OwnedMenuItem>,\n    {\n        for item in items {\n            match item.into() {\n                OwnedMenuItem::Action {\n                    name,\n                    action,\n                    checked,\n                    ..\n                } => self = self.menu_with_check(name, checked, action.boxed_clone()),\n                OwnedMenuItem::Separator => {\n                    self = self.separator();\n                }\n                OwnedMenuItem::Submenu(submenu) => {\n                    self = self.submenu(submenu.name, window, cx, move |menu, window, cx| {\n                        menu.with_menu_items(submenu.items.clone(), window, cx)\n                    })\n                }\n                OwnedMenuItem::SystemMenu(_) => {}\n            }\n        }\n\n        if self.menu_items.len() > 20 {\n            self.scrollable = true;\n        }\n\n        self\n    }\n\n    pub(crate) fn active_submenu(&self) -> Option<Entity<PopupMenu>> {\n        if let Some(ix) = self.selected_index {\n            if let Some(item) = self.menu_items.get(ix) {\n                return match item {\n                    PopupMenuItem::Submenu { menu, .. } => Some(menu.clone()),\n                    _ => None,\n                };\n            }\n        }\n\n        None\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.menu_items.is_empty()\n    }\n\n    fn clickable_menu_items(&self) -> impl Iterator<Item = (usize, &PopupMenuItem)> {\n        self.menu_items\n            .iter()\n            .enumerate()\n            .filter(|(_, item)| item.is_clickable())\n    }\n\n    fn on_click(&mut self, ix: usize, window: &mut Window, cx: &mut Context<Self>) {\n        cx.stop_propagation();\n        window.prevent_default();\n        self.selected_index = Some(ix);\n        self.confirm(&Confirm { secondary: false }, window, cx);\n    }\n\n    fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {\n        match self.selected_index {\n            Some(index) => {\n                let item = self.menu_items.get(index);\n                match item {\n                    Some(PopupMenuItem::Item {\n                        handler, action, ..\n                    }) => {\n                        if let Some(handler) = handler {\n                            handler(&ClickEvent::default(), window, cx);\n                        } else if let Some(action) = action.as_ref() {\n                            self.dispatch_confirm_action(action, window, cx);\n                        }\n\n                        self.dismiss(&Cancel, window, cx)\n                    }\n                    Some(PopupMenuItem::ElementItem {\n                        handler, action, ..\n                    }) => {\n                        if let Some(handler) = handler {\n                            handler(&ClickEvent::default(), window, cx);\n                        } else if let Some(action) = action.as_ref() {\n                            self.dispatch_confirm_action(action, window, cx);\n                        }\n                        self.dismiss(&Cancel, window, cx)\n                    }\n                    _ => {}\n                }\n            }\n            _ => {}\n        }\n    }\n\n    fn dispatch_confirm_action(\n        &self,\n        action: &Box<dyn Action>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if let Some(context) = self.action_context.as_ref() {\n            context.focus(window, cx);\n        }\n\n        window.dispatch_action(action.boxed_clone(), cx);\n    }\n\n    fn set_selected_index(&mut self, ix: usize, cx: &mut Context<Self>) {\n        if self.selected_index != Some(ix) {\n            self.selected_index = Some(ix);\n            self.scroll_handle.scroll_to_item(ix);\n            cx.notify();\n        }\n    }\n\n    fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {\n        cx.stop_propagation();\n        let ix = self.selected_index.unwrap_or(0);\n\n        if let Some((prev_ix, _)) = self\n            .menu_items\n            .iter()\n            .enumerate()\n            .rev()\n            .find(|(i, item)| *i < ix && item.is_clickable())\n        {\n            self.set_selected_index(prev_ix, cx);\n            return;\n        }\n\n        let last_clickable_ix = self.clickable_menu_items().last().map(|(ix, _)| ix);\n        self.set_selected_index(last_clickable_ix.unwrap_or(0), cx);\n    }\n\n    fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {\n        cx.stop_propagation();\n        let Some(ix) = self.selected_index else {\n            self.set_selected_index(0, cx);\n            return;\n        };\n\n        if let Some((next_ix, _)) = self\n            .menu_items\n            .iter()\n            .enumerate()\n            .find(|(i, item)| *i > ix && item.is_clickable())\n        {\n            self.set_selected_index(next_ix, cx);\n            return;\n        }\n\n        self.set_selected_index(0, cx);\n    }\n\n    fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {\n        let handled = if matches!(self.submenu_anchor.0, Corner::TopLeft | Corner::BottomLeft) {\n            self._unselect_submenu(window, cx)\n        } else {\n            self._select_submenu(window, cx)\n        };\n\n        if self.parent_side(cx).is_left() {\n            self._focus_parent_menu(window, cx);\n        }\n\n        if handled {\n            return;\n        }\n\n        // For parent AppMenuBar to handle.\n        if self.parent_menu.is_none() {\n            cx.propagate();\n        }\n    }\n\n    fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {\n        let handled = if matches!(self.submenu_anchor.0, Corner::TopLeft | Corner::BottomLeft) {\n            self._select_submenu(window, cx)\n        } else {\n            self._unselect_submenu(window, cx)\n        };\n\n        if self.parent_side(cx).is_right() {\n            self._focus_parent_menu(window, cx);\n        }\n\n        if handled {\n            return;\n        }\n\n        // For parent AppMenuBar to handle.\n        if self.parent_menu.is_none() {\n            cx.propagate();\n        }\n    }\n\n    fn _select_submenu(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {\n        if let Some(active_submenu) = self.active_submenu() {\n            // Focus the submenu, so that can be handle the action.\n            active_submenu.update(cx, |view, cx| {\n                view.set_selected_index(0, cx);\n                view.focus_handle.focus(window, cx);\n            });\n            cx.notify();\n            return true;\n        }\n\n        return false;\n    }\n\n    fn _unselect_submenu(&mut self, _: &mut Window, cx: &mut Context<Self>) -> bool {\n        if let Some(active_submenu) = self.active_submenu() {\n            active_submenu.update(cx, |view, cx| {\n                view.selected_index = None;\n                cx.notify();\n            });\n            return true;\n        }\n\n        return false;\n    }\n\n    fn _focus_parent_menu(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        let Some(parent) = self.parent_menu.as_ref() else {\n            return;\n        };\n        let Some(parent) = parent.upgrade() else {\n            return;\n        };\n\n        self.selected_index = None;\n        parent.update(cx, |view, cx| {\n            view.focus_handle.focus(window, cx);\n            cx.notify();\n        });\n    }\n\n    fn parent_side(&self, cx: &App) -> Side {\n        let Some(parent) = self.parent_menu.as_ref() else {\n            return Side::Left;\n        };\n\n        let Some(parent) = parent.upgrade() else {\n            return Side::Left;\n        };\n\n        match parent.read(cx).submenu_anchor.0 {\n            Corner::TopLeft | Corner::BottomLeft => Side::Left,\n            Corner::TopRight | Corner::BottomRight => Side::Right,\n        }\n    }\n\n    fn dismiss(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {\n        if self.active_submenu().is_some() {\n            return;\n        }\n\n        cx.emit(DismissEvent);\n\n        // Focus back to the previous focused handle.\n        if let Some(action_context) = self.action_context.as_ref() {\n            window.focus(action_context, cx);\n        }\n\n        let Some(parent_menu) = self.parent_menu.clone() else {\n            return;\n        };\n\n        // Dismiss parent menu, when this menu is dismissed\n        _ = parent_menu.update(cx, |view, cx| {\n            view.selected_index = None;\n            view.dismiss(&Cancel, window, cx);\n        });\n    }\n\n    fn handle_dismiss(\n        &mut self,\n        position: &Point<Pixels>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        // Do not dismiss, if click inside the parent menu\n        if let Some(parent) = self.parent_menu.as_ref() {\n            if let Some(parent) = parent.upgrade() {\n                if parent.read(cx).bounds.contains(position) {\n                    return;\n                }\n            }\n        }\n\n        self.dismiss(&Cancel, window, cx);\n    }\n\n    fn on_mouse_down_out(\n        &mut self,\n        e: &MouseDownEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.handle_dismiss(&e.position, window, cx);\n    }\n\n    fn render_key_binding(\n        &self,\n        action: Option<Box<dyn Action>>,\n        window: &mut Window,\n        _: &mut Context<Self>,\n    ) -> Option<Kbd> {\n        let action = action?;\n\n        match self\n            .action_context\n            .as_ref()\n            .and_then(|handle| Kbd::binding_for_action_in(action.as_ref(), handle, window))\n        {\n            Some(kbd) => Some(kbd),\n            // Fallback to App level key binding\n            None => Kbd::binding_for_action(action.as_ref(), None, window),\n        }\n        .map(|this| {\n            this.p_0()\n                .flex_nowrap()\n                .border_0()\n                .bg(gpui::transparent_white())\n        })\n    }\n\n    fn render_icon(\n        has_icon: bool,\n        checked: bool,\n        icon: Option<Icon>,\n        _: &mut Window,\n        _: &mut Context<Self>,\n    ) -> Option<impl IntoElement> {\n        if !has_icon {\n            return None;\n        }\n\n        let icon = if let Some(icon) = icon {\n            icon.clone()\n        } else if checked {\n            Icon::new(IconName::Check)\n        } else {\n            Icon::empty()\n        };\n\n        Some(icon.xsmall())\n    }\n\n    #[inline]\n    fn max_width(&self) -> Pixels {\n        self.max_width.unwrap_or(px(500.))\n    }\n\n    /// Calculate the anchor corner and left offset for child submenu\n    fn update_submenu_menu_anchor(&mut self, window: &Window) {\n        let bounds = self.bounds;\n        let max_width = self.max_width();\n        let (anchor, left) = if max_width + bounds.origin.x > window.bounds().size.width {\n            (Corner::TopRight, -px(16.))\n        } else {\n            (Corner::TopLeft, bounds.size.width - px(8.))\n        };\n\n        let is_bottom_pos = bounds.origin.y + bounds.size.height > window.bounds().size.height;\n        self.submenu_anchor = if is_bottom_pos {\n            (anchor.other_side_corner_along(gpui::Axis::Vertical), left)\n        } else {\n            (anchor, left)\n        };\n    }\n\n    fn render_item(\n        &self,\n        ix: usize,\n        item: &PopupMenuItem,\n        options: RenderOptions,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> MenuItemElement {\n        let has_left_icon = options.has_left_icon;\n        let is_left_check = options.check_side.is_left() && item.is_checked();\n        let right_check_icon = if options.check_side.is_right() && item.is_checked() {\n            Some(Icon::new(IconName::Check).xsmall())\n        } else {\n            None\n        };\n\n        let selected = self.selected_index == Some(ix);\n        const EDGE_PADDING: Pixels = px(4.);\n        const INNER_PADDING: Pixels = px(8.);\n\n        let is_submenu = matches!(item, PopupMenuItem::Submenu { .. });\n        let group_name = format!(\"{}:item-{}\", cx.entity().entity_id(), ix);\n\n        let (item_height, radius) = match self.size {\n            Size::Small => (px(20.), options.radius.half()),\n            _ => (px(26.), options.radius),\n        };\n\n        let this = MenuItemElement::new(ix, &group_name)\n            .relative()\n            .text_sm()\n            .py_0()\n            .px(INNER_PADDING)\n            .rounded(radius)\n            .items_center()\n            .selected(selected)\n            .on_hover(cx.listener(move |this, hovered, _, cx| {\n                if *hovered {\n                    this.selected_index = Some(ix);\n                } else if !is_submenu && this.selected_index == Some(ix) {\n                    // TODO: Better handle the submenu unselection when hover out\n                    this.selected_index = None;\n                }\n\n                cx.notify();\n            }));\n\n        match item {\n            PopupMenuItem::Separator => this\n                .h_auto()\n                .p_0()\n                .my_0p5()\n                .mx_neg_1()\n                .border_b(px(2.))\n                .border_color(cx.theme().border)\n                .disabled(true),\n            PopupMenuItem::Label(label) => this.disabled(true).cursor_default().child(\n                h_flex()\n                    .cursor_default()\n                    .items_center()\n                    .gap_x_1()\n                    .children(Self::render_icon(has_left_icon, false, None, window, cx))\n                    .child(div().flex_1().child(label.clone())),\n            ),\n            PopupMenuItem::ElementItem {\n                render,\n                icon,\n                disabled,\n                ..\n            } => this\n                .when(!disabled, |this| {\n                    this.on_click(\n                        cx.listener(move |this, _, window, cx| this.on_click(ix, window, cx)),\n                    )\n                })\n                .disabled(*disabled)\n                .child(\n                    h_flex()\n                        .flex_1()\n                        .min_h(item_height)\n                        .items_center()\n                        .gap_x_1()\n                        .children(Self::render_icon(\n                            has_left_icon,\n                            is_left_check,\n                            icon.clone(),\n                            window,\n                            cx,\n                        ))\n                        .child((render)(window, cx))\n                        .children(right_check_icon.map(|icon| icon.ml_3())),\n                ),\n            PopupMenuItem::Item {\n                icon,\n                label,\n                action,\n                disabled,\n                is_link,\n                ..\n            } => {\n                let show_link_icon = *is_link && self.external_link_icon;\n                let action = action.as_ref().map(|action| action.boxed_clone());\n                let key = self.render_key_binding(action, window, cx);\n\n                this.when(!disabled, |this| {\n                    this.on_click(\n                        cx.listener(move |this, _, window, cx| this.on_click(ix, window, cx)),\n                    )\n                })\n                .disabled(*disabled)\n                .h(item_height)\n                .gap_x_1()\n                .children(Self::render_icon(\n                    has_left_icon,\n                    is_left_check,\n                    icon.clone(),\n                    window,\n                    cx,\n                ))\n                .child(\n                    h_flex()\n                        .w_full()\n                        .gap_3()\n                        .items_center()\n                        .justify_between()\n                        .when(!show_link_icon, |this| this.child(label.clone()))\n                        .children(right_check_icon)\n                        .when(show_link_icon, |this| {\n                            this.child(\n                                h_flex()\n                                    .w_full()\n                                    .justify_between()\n                                    .gap_1p5()\n                                    .child(label.clone())\n                                    .child(\n                                        Icon::new(IconName::ExternalLink)\n                                            .xsmall()\n                                            .text_color(cx.theme().muted_foreground),\n                                    ),\n                            )\n                        })\n                        .children(key),\n                )\n            }\n            PopupMenuItem::Submenu {\n                icon,\n                label,\n                menu,\n                disabled,\n            } => this\n                .selected(selected)\n                .disabled(*disabled)\n                .items_start()\n                .child(\n                    h_flex()\n                        .min_h(item_height)\n                        .size_full()\n                        .items_center()\n                        .gap_x_1()\n                        .children(Self::render_icon(\n                            has_left_icon,\n                            false,\n                            icon.clone(),\n                            window,\n                            cx,\n                        ))\n                        .child(\n                            h_flex()\n                                .flex_1()\n                                .gap_2()\n                                .items_center()\n                                .justify_between()\n                                .child(label.clone())\n                                .child(\n                                    Icon::new(IconName::ChevronRight)\n                                        .xsmall()\n                                        .text_color(cx.theme().muted_foreground),\n                                ),\n                        ),\n                )\n                .when(selected, |this| {\n                    this.child({\n                        let (anchor, left) = self.submenu_anchor;\n                        let is_bottom_pos =\n                            matches!(anchor, Corner::BottomLeft | Corner::BottomRight);\n                        anchored()\n                            .anchor(anchor)\n                            .child(\n                                div()\n                                    .id(\"submenu\")\n                                    .occlude()\n                                    .when(is_bottom_pos, |this| this.bottom_0())\n                                    .when(!is_bottom_pos, |this| this.top_neg_1())\n                                    .left(left)\n                                    .child(menu.clone()),\n                            )\n                            .snap_to_window_with_margin(Edges::all(EDGE_PADDING))\n                    })\n                }),\n        }\n    }\n}\n\nimpl FluentBuilder for PopupMenu {}\nimpl EventEmitter<DismissEvent> for PopupMenu {}\nimpl Focusable for PopupMenu {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\n#[derive(Clone, Copy)]\nstruct RenderOptions {\n    has_left_icon: bool,\n    check_side: Side,\n    radius: Pixels,\n}\n\nimpl Render for PopupMenu {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        self.update_submenu_menu_anchor(window);\n\n        let view = cx.entity().clone();\n        let items_count = self.menu_items.len();\n\n        let max_height = self.max_height.unwrap_or_else(|| {\n            let window_half_height = window.window_bounds().get_bounds().size.height * 0.5;\n            window_half_height.min(px(450.))\n        });\n\n        let has_left_icon = self\n            .menu_items\n            .iter()\n            .any(|item| item.has_left_icon(self.check_side));\n\n        let max_width = self.max_width();\n        let options = RenderOptions {\n            has_left_icon,\n            check_side: self.check_side,\n            radius: cx.theme().radius.min(px(8.)),\n        };\n\n        v_flex()\n            .id(\"popup-menu\")\n            .key_context(CONTEXT)\n            .track_focus(&self.focus_handle)\n            .on_action(cx.listener(Self::select_up))\n            .on_action(cx.listener(Self::select_down))\n            .on_action(cx.listener(Self::select_left))\n            .on_action(cx.listener(Self::select_right))\n            .on_action(cx.listener(Self::confirm))\n            .on_action(cx.listener(Self::dismiss))\n            .on_mouse_down_out(cx.listener(Self::on_mouse_down_out))\n            .popover_style(cx)\n            .text_color(cx.theme().popover_foreground)\n            .relative()\n            .occlude()\n            .child(\n                v_flex()\n                    .id(\"items\")\n                    .p_1()\n                    .gap_y_0p5()\n                    .min_w(rems(8.))\n                    .when_some(self.min_width, |this, min_width| this.min_w(min_width))\n                    .max_w(max_width)\n                    .when(self.scrollable, |this| {\n                        this.max_h(max_height)\n                            .overflow_y_scroll()\n                            .track_scroll(&self.scroll_handle)\n                    })\n                    .children(\n                        self.menu_items\n                            .iter()\n                            .enumerate()\n                            // Ignore last separator\n                            .filter(|(ix, item)| !(*ix + 1 == items_count && item.is_separator()))\n                            .map(|(ix, item)| self.render_item(ix, item, options, window, cx)),\n                    )\n                    .on_prepaint(move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds)),\n            )\n            .when(self.scrollable, |this| {\n                // TODO: When the menu is limited by `overflow_y_scroll`, the sub-menu will cannot be displayed.\n                this.vertical_scrollbar(&self.scroll_handle)\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/notification.rs",
    "content": "use std::{\n    any::TypeId,\n    collections::{HashMap, VecDeque},\n    rc::Rc,\n    time::Duration,\n};\n\nuse gpui::{\n    Animation, AnimationExt, AnyElement, App, AppContext, ClickEvent, Context, DismissEvent,\n    ElementId, Entity, EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _,\n    Pixels, Render, SharedString, StatefulInteractiveElement, StyleRefinement, Styled,\n    Subscription, Window, div, prelude::FluentBuilder, px,\n};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\nuse crate::{\n    ActiveTheme as _, Anchor, Edges, Icon, IconName, Sizable as _, StyledExt, TITLE_BAR_HEIGHT,\n    animation::cubic_bezier,\n    button::{Button, ButtonVariants as _},\n    h_flex, v_flex,\n};\n\n#[derive(Debug, Clone, Copy, Default)]\npub enum NotificationType {\n    #[default]\n    Info,\n    Success,\n    Warning,\n    Error,\n}\n\nimpl NotificationType {\n    fn icon(&self, cx: &App) -> Icon {\n        match self {\n            Self::Info => Icon::new(IconName::Info).text_color(cx.theme().info),\n            Self::Success => Icon::new(IconName::CircleCheck).text_color(cx.theme().success),\n            Self::Warning => Icon::new(IconName::TriangleAlert).text_color(cx.theme().warning),\n            Self::Error => Icon::new(IconName::CircleX).text_color(cx.theme().danger),\n        }\n    }\n}\n\n#[derive(Debug, PartialEq, Clone, Hash, Eq)]\npub(crate) enum NotificationId {\n    Id(TypeId),\n    IdAndElementId(TypeId, ElementId),\n}\n\nimpl From<TypeId> for NotificationId {\n    fn from(type_id: TypeId) -> Self {\n        Self::Id(type_id)\n    }\n}\n\nimpl From<(TypeId, ElementId)> for NotificationId {\n    fn from((type_id, id): (TypeId, ElementId)) -> Self {\n        Self::IdAndElementId(type_id, id)\n    }\n}\n\n/// A notification element.\npub struct Notification {\n    /// The id is used make the notification unique.\n    /// Then you push a notification with the same id, the previous notification will be replaced.\n    ///\n    /// None means the notification will be added to the end of the list.\n    id: NotificationId,\n    style: StyleRefinement,\n    type_: Option<NotificationType>,\n    title: Option<SharedString>,\n    message: Option<SharedString>,\n    icon: Option<Icon>,\n    autohide: bool,\n    action_builder: Option<Rc<dyn Fn(&mut Self, &mut Window, &mut Context<Self>) -> Button>>,\n    content_builder: Option<Rc<dyn Fn(&mut Self, &mut Window, &mut Context<Self>) -> AnyElement>>,\n    on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,\n    closing: bool,\n}\n\nimpl From<String> for Notification {\n    fn from(s: String) -> Self {\n        Self::new().message(s)\n    }\n}\n\nimpl From<SharedString> for Notification {\n    fn from(s: SharedString) -> Self {\n        Self::new().message(s)\n    }\n}\n\nimpl From<&'static str> for Notification {\n    fn from(s: &'static str) -> Self {\n        Self::new().message(s)\n    }\n}\n\nimpl From<(NotificationType, &'static str)> for Notification {\n    fn from((type_, content): (NotificationType, &'static str)) -> Self {\n        Self::new().message(content).with_type(type_)\n    }\n}\n\nimpl From<(NotificationType, SharedString)> for Notification {\n    fn from((type_, content): (NotificationType, SharedString)) -> Self {\n        Self::new().message(content).with_type(type_)\n    }\n}\n\nstruct DefaultIdType;\n\nimpl Notification {\n    /// Create a new notification.\n    ///\n    /// The default id is a random UUID.\n    pub fn new() -> Self {\n        let id: SharedString = uuid::Uuid::new_v4().to_string().into();\n        let id = (TypeId::of::<DefaultIdType>(), id.into());\n\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default(),\n            title: None,\n            message: None,\n            type_: None,\n            icon: None,\n            autohide: true,\n            action_builder: None,\n            content_builder: None,\n            on_click: None,\n            closing: false,\n        }\n    }\n\n    /// Set the message of the notification, default is None.\n    pub fn message(mut self, message: impl Into<SharedString>) -> Self {\n        self.message = Some(message.into());\n        self\n    }\n\n    /// Create an info notification with the given message.\n    pub fn info(message: impl Into<SharedString>) -> Self {\n        Self::new()\n            .message(message)\n            .with_type(NotificationType::Info)\n    }\n\n    /// Create a success notification with the given message.\n    pub fn success(message: impl Into<SharedString>) -> Self {\n        Self::new()\n            .message(message)\n            .with_type(NotificationType::Success)\n    }\n\n    /// Create a warning notification with the given message.\n    pub fn warning(message: impl Into<SharedString>) -> Self {\n        Self::new()\n            .message(message)\n            .with_type(NotificationType::Warning)\n    }\n\n    /// Create an error notification with the given message.\n    pub fn error(message: impl Into<SharedString>) -> Self {\n        Self::new()\n            .message(message)\n            .with_type(NotificationType::Error)\n    }\n\n    /// Set the type for unique identification of the notification.\n    ///\n    /// ```rs\n    /// struct MyNotificationKind;\n    /// let notification = Notification::new(\"Hello\").id::<MyNotificationKind>();\n    /// ```\n    pub fn id<T: Sized + 'static>(mut self) -> Self {\n        self.id = TypeId::of::<T>().into();\n        self\n    }\n\n    /// Set the type and id of the notification, used to uniquely identify the notification.\n    pub fn id1<T: Sized + 'static>(mut self, key: impl Into<ElementId>) -> Self {\n        self.id = (TypeId::of::<T>(), key.into()).into();\n        self\n    }\n\n    /// Set the title of the notification, default is None.\n    ///\n    /// If title is None, the notification will not have a title.\n    pub fn title(mut self, title: impl Into<SharedString>) -> Self {\n        self.title = Some(title.into());\n        self\n    }\n\n    /// Set the icon of the notification.\n    ///\n    /// If icon is None, the notification will use the default icon of the type.\n    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {\n        self.icon = Some(icon.into());\n        self\n    }\n\n    /// Set the type of the notification, default is NotificationType::Info.\n    pub fn with_type(mut self, type_: NotificationType) -> Self {\n        self.type_ = Some(type_);\n        self\n    }\n\n    /// Set the auto hide of the notification, default is true.\n    pub fn autohide(mut self, autohide: bool) -> Self {\n        self.autohide = autohide;\n        self\n    }\n\n    /// Set the click callback of the notification.\n    pub fn on_click(\n        mut self,\n        on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_click = Some(Rc::new(on_click));\n        self\n    }\n\n    /// Set the action button of the notification.\n    ///\n    /// When an action is set, the notification will not autohide.\n    pub fn action<F>(mut self, action: F) -> Self\n    where\n        F: Fn(&mut Self, &mut Window, &mut Context<Self>) -> Button + 'static,\n    {\n        self.action_builder = Some(Rc::new(action));\n        self.autohide = false;\n        self\n    }\n\n    /// Dismiss the notification.\n    pub fn dismiss(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        if self.closing {\n            return;\n        }\n        self.closing = true;\n        cx.notify();\n\n        // Dismiss the notification after 0.15s to show the animation.\n        cx.spawn(async move |view, cx| {\n            cx.background_executor().timer(Duration::from_secs_f32(0.15)).await;\n            cx.update(|cx| {\n                if let Some(view) = view.upgrade() {\n                    view.update(cx, |view, cx| {\n                        view.closing = false;\n                        cx.emit(DismissEvent);\n                    });\n                }\n            })\n        })\n        .detach();\n    }\n\n    /// Set the content of the notification.\n    pub fn content(\n        mut self,\n        content: impl Fn(&mut Self, &mut Window, &mut Context<Self>) -> AnyElement + 'static,\n    ) -> Self {\n        self.content_builder = Some(Rc::new(content));\n        self\n    }\n}\nimpl EventEmitter<DismissEvent> for Notification {}\nimpl FluentBuilder for Notification {}\nimpl Styled for Notification {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\nimpl Render for Notification {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let content = self\n            .content_builder\n            .clone()\n            .map(|builder| builder(self, window, cx));\n        let action = self\n            .action_builder\n            .clone()\n            .map(|builder| builder(self, window, cx).small().mr_3p5());\n\n        let closing = self.closing;\n        let icon = match self.type_ {\n            None => self.icon.clone(),\n            Some(type_) => Some(type_.icon(cx)),\n        };\n        let has_icon = icon.is_some();\n        let placement = cx.theme().notification.placement;\n\n        h_flex()\n            .id(\"notification\")\n            .group(\"\")\n            .occlude()\n            .relative()\n            .w_112()\n            .border_1()\n            .border_color(cx.theme().border)\n            .bg(cx.theme().popover)\n            .rounded(cx.theme().radius_lg)\n            .shadow_md()\n            .py_3p5()\n            .px_4()\n            .gap_3()\n            .refine_style(&self.style)\n            .when_some(icon, |this, icon| {\n                this.child(div().absolute().py_3p5().left_4().child(icon))\n            })\n            .child(\n                v_flex()\n                    .flex_1()\n                    .overflow_hidden()\n                    .when(has_icon, |this| this.pl_6())\n                    .when_some(self.title.clone(), |this, title| {\n                        this.child(div().text_sm().font_semibold().child(title))\n                    })\n                    .when_some(self.message.clone(), |this, message| {\n                        this.child(div().text_sm().child(message))\n                    })\n                    .when_some(content, |this, content| this.child(content)),\n            )\n            .when_some(action, |this, action| this.child(action))\n            .child(\n                div()\n                    .absolute()\n                    .top_1()\n                    .right_1()\n                    .invisible()\n                    .group_hover(\"\", |this| this.visible())\n                    .child(\n                        Button::new(\"close\")\n                            .icon(IconName::Close)\n                            .ghost()\n                            .xsmall()\n                            .on_click(cx.listener(|this, _, window, cx| this.dismiss(window, cx))),\n                    ),\n            )\n            .when_some(self.on_click.clone(), |this, on_click| {\n                this.on_click(cx.listener(move |view, event, window, cx| {\n                    view.dismiss(window, cx);\n                    on_click(event, window, cx);\n                }))\n            })\n            .on_aux_click(cx.listener(move |view, event: &ClickEvent, window, cx| {\n                if event.is_middle_click() {\n                    view.dismiss(window, cx);\n                }\n            }))\n            .with_animation(\n                ElementId::NamedInteger(\"slide-down\".into(), closing as u64),\n                Animation::new(Duration::from_secs_f64(0.25))\n                    .with_easing(cubic_bezier(0.4, 0., 0.2, 1.)),\n                move |this, delta| {\n                    if closing {\n                        let opacity = 1. - delta;\n                        let that = this\n                            .shadow_none()\n                            .opacity(opacity)\n                            .when(opacity < 0.85, |this| this.shadow_none());\n                        match placement {\n                            Anchor::TopRight | Anchor::BottomRight => {\n                                let x_offset = px(0.) + delta * px(45.);\n                                that.left(px(0.) + x_offset)\n                            }\n                            Anchor::TopLeft | Anchor::BottomLeft => {\n                                let x_offset = px(0.) - delta * px(45.);\n                                that.left(px(0.) + x_offset)\n                            }\n                            Anchor::TopCenter => {\n                                let y_offset = px(0.) - delta * px(45.);\n                                that.top(px(0.) + y_offset)\n                            }\n                            Anchor::BottomCenter => {\n                                let y_offset = px(0.) + delta * px(45.);\n                                that.top(px(0.) + y_offset)\n                            }\n                        }\n                    } else {\n                        let y_offset = match placement {\n                            placement if placement.is_top() => px(-45.) + delta * px(45.),\n                            placement if placement.is_bottom() => px(45.) - delta * px(45.),\n                            _ => px(0.),\n                        };\n                        let opacity = delta;\n                        this.top(px(0.) + y_offset)\n                            .opacity(opacity)\n                            .when(opacity < 0.85, |this| this.shadow_none())\n                    }\n                },\n            )\n    }\n}\n\n/// The settings for notifications.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct NotificationSettings {\n    /// The placement of the notification, default: [`Anchor::TopRight`]\n    pub placement: Anchor,\n    /// The margins of the notification with respect to the window edges.\n    pub margins: Edges<Pixels>,\n    /// The maximum number of notifications to show at once, default: 10\n    pub max_items: usize,\n}\n\nimpl Default for NotificationSettings {\n    fn default() -> Self {\n        let offset = px(16.);\n        Self {\n            placement: Anchor::TopRight,\n            margins: Edges {\n                top: TITLE_BAR_HEIGHT + offset, // avoid overlap with title bar\n                right: offset,\n                bottom: offset,\n                left: offset,\n            },\n            max_items: 10,\n        }\n    }\n}\n\n/// A list of notifications.\npub struct NotificationList {\n    /// Notifications that will be auto hidden.\n    pub(crate) notifications: VecDeque<Entity<Notification>>,\n    expanded: bool,\n    _subscriptions: HashMap<NotificationId, Subscription>,\n}\n\nimpl NotificationList {\n    pub fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {\n        Self {\n            notifications: VecDeque::new(),\n            expanded: false,\n            _subscriptions: HashMap::new(),\n        }\n    }\n\n    pub fn push(\n        &mut self,\n        notification: impl Into<Notification>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let notification = notification.into();\n        let id = notification.id.clone();\n        let autohide = notification.autohide;\n\n        // Remove the notification by id, for keep unique.\n        self.notifications.retain(|note| note.read(cx).id != id);\n\n        let notification = cx.new(|_| notification);\n\n        self._subscriptions.insert(\n            id.clone(),\n            cx.subscribe(&notification, move |view, _, _: &DismissEvent, cx| {\n                view.notifications.retain(|note| id != note.read(cx).id);\n                view._subscriptions.remove(&id);\n            }),\n        );\n\n        self.notifications.push_back(notification.clone());\n        if autohide {\n            // Sleep for 5 seconds to autohide the notification\n            cx.spawn_in(window, async move |_, cx| {\n                cx.background_executor().timer(Duration::from_secs(5)).await;\n\n                if let Err(err) =\n                    notification.update_in(cx, |note, window, cx| note.dismiss(window, cx))\n                {\n                    tracing::error!(\"failed to auto hide notification: {:?}\", err);\n                }\n            })\n            .detach();\n        }\n        cx.notify();\n    }\n\n    pub(crate) fn close(\n        &mut self,\n        id: impl Into<NotificationId>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let id: NotificationId = id.into();\n        if let Some(n) = self.notifications.iter().find(|n| n.read(cx).id == id) {\n            n.update(cx, |note, cx| note.dismiss(window, cx))\n        }\n        cx.notify();\n    }\n\n    pub fn clear(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        self.notifications.clear();\n        cx.notify();\n    }\n\n    pub fn notifications(&self) -> Vec<Entity<Notification>> {\n        self.notifications.iter().cloned().collect()\n    }\n}\n\nimpl Render for NotificationList {\n    fn render(\n        &mut self,\n        window: &mut gpui::Window,\n        cx: &mut gpui::Context<Self>,\n    ) -> impl IntoElement {\n        let size = window.viewport_size();\n        let items = self.notifications.iter().rev().take(10).rev().cloned();\n\n        let placement = cx.theme().notification.placement;\n        let margins = &cx.theme().notification.margins;\n\n        v_flex()\n            .id(\"notification-list\")\n            .max_h(size.height)\n            .pt(margins.top)\n            .pb(margins.bottom)\n            .gap_3()\n            .when(\n                matches!(placement, Anchor::TopRight),\n                |this| this.pr(margins.right), // ignore left\n            )\n            .when(\n                matches!(placement, Anchor::TopLeft),\n                |this| this.pl(margins.left), // ignore right\n            )\n            .when(\n                matches!(placement, Anchor::BottomLeft),\n                |this| this.flex_col_reverse().pl(margins.left), // ignore right\n            )\n            .when(\n                matches!(placement, Anchor::BottomRight),\n                |this| this.flex_col_reverse().pr(margins.right), // ignore left\n            )\n            .when(matches!(placement, Anchor::BottomCenter), |this| {\n                this.flex_col_reverse()\n            })\n            .on_hover(cx.listener(|view, hovered, _, cx| {\n                view.expanded = *hovered;\n                cx.notify()\n            }))\n            .children(items)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/pagination.rs",
    "content": "use std::{ops::Range, rc::Rc};\n\nuse gpui::{\n    App, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce, SharedString,\n    StyleRefinement, Styled, Window, prelude::FluentBuilder, px,\n};\nuse rust_i18n::t;\n\nuse crate::{\n    Disableable, Icon, Sizable, Size, StyledExt,\n    button::{Button, ButtonVariants},\n    h_flex,\n    icon::IconName,\n    menu::{DropdownMenu as _, PopupMenuItem},\n};\n\n/// Pagination with page navigation, next and previous links.\n#[derive(IntoElement)]\npub struct Pagination {\n    id: ElementId,\n    style: StyleRefinement,\n    size: Size,\n    current_page: usize,\n    total_pages: usize,\n    disabled: bool,\n    compact: bool,\n    visible_pages: usize,\n    on_click: Option<Rc<dyn Fn(&usize, &mut Window, &mut App)>>,\n}\n\n#[derive(Debug, Clone, Eq, PartialEq)]\nenum PageItem {\n    Page(usize),\n    Ellipsis(Range<usize>),\n}\n\nimpl Pagination {\n    /// Create a new Pagination component with the given ID.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default(),\n            size: Size::default(),\n            current_page: 1,\n            total_pages: 1,\n            visible_pages: 5,\n            disabled: false,\n            compact: false,\n            on_click: None,\n        }\n    }\n\n    /// Set the current page number (1-based).\n    ///\n    /// The value will be clamped between 1 and total_pages when total_pages is set.\n    pub fn current_page(mut self, page: usize) -> Self {\n        self.current_page = page.max(1);\n        self\n    }\n\n    /// Set the total number of pages.\n    pub fn total_pages(mut self, pages: usize) -> Self {\n        self.total_pages = pages.max(1);\n        if self.current_page > self.total_pages {\n            self.current_page = self.total_pages;\n        }\n        self\n    }\n\n    /// Set the handler for page change (when clicking on page numbers, prev, or next).\n    ///\n    /// This handler receives the new page number to navigate to.\n    ///\n    /// # Examples\n    ///\n    /// ```ignore\n    /// Pagination::new(\"my-pagination\")\n    ///     .current_page(current_page)\n    ///     .total_pages(total_pages)\n    ///     .on_click(|page, _, cx| {\n    ///         // Handle page change\n    ///     })\n    /// ```\n    pub fn on_click(mut self, handler: impl Fn(&usize, &mut Window, &mut App) + 'static) -> Self {\n        self.on_click = Some(Rc::new(handler));\n        self\n    }\n\n    /// Set to display as compact style.\n    ///\n    /// If true, only the prev, next buttons with only icon.\n    pub fn compact(mut self) -> Self {\n        self.compact = true;\n        self\n    }\n\n    /// Set viewable maximum number of page buttons, default\n    pub fn visible_pages(mut self, max: usize) -> Self {\n        self.visible_pages = max;\n        self\n    }\n\n    fn render_nav_button(&self, is_prev: bool) -> Button {\n        let (id, label, icon, disabled) = if is_prev {\n            (\n                \"prev\",\n                t!(\"Pagination.previous\"),\n                IconName::ChevronLeft,\n                self.current_page <= 1,\n            )\n        } else {\n            (\n                \"next\",\n                t!(\"Pagination.next\"),\n                IconName::ChevronRight,\n                self.current_page >= self.total_pages,\n            )\n        };\n\n        let target_page = if is_prev {\n            self.current_page.saturating_sub(1)\n        } else {\n            self.current_page.saturating_add(1)\n        };\n\n        Button::new(id)\n            .ghost()\n            .compact()\n            .with_size(self.size)\n            .disabled(self.disabled || disabled)\n            .tooltip(label.clone())\n            .when(self.compact, |this| this.icon(icon.clone()))\n            .when(!self.compact, |this| {\n                this.child(\n                    h_flex()\n                        .w_full()\n                        .gap_2()\n                        .flex_nowrap()\n                        .when(is_prev, |this| this.flex_row_reverse())\n                        .child(SharedString::from(label))\n                        .child(Icon::new(icon)),\n                )\n            })\n            .when_some(self.on_click.clone(), |this, handler| {\n                this.on_click(move |_, window, cx| {\n                    handler(&target_page, window, cx);\n                })\n            })\n    }\n}\n\nimpl Disableable for Pagination {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl Sizable for Pagination {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Styled for Pagination {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Pagination {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        let page_numbers = if !self.compact {\n            calculate_page_range(self.current_page, self.total_pages, self.visible_pages)\n        } else {\n            vec![]\n        };\n\n        let current_page = self.current_page;\n        let is_disabled = self.disabled;\n        let on_click = self.on_click.clone();\n\n        h_flex()\n            .id(self.id.clone())\n            .px_2()\n            .py_2()\n            .gap_1()\n            .items_center()\n            .refine_style(&self.style)\n            .child(self.render_nav_button(true))\n            .children({\n                page_numbers.into_iter().map(|item| match item {\n                    PageItem::Page(page) => {\n                        let is_selected = page == current_page;\n\n                        Button::new(page)\n                            .with_size(self.size)\n                            .map(|this| {\n                                if is_selected {\n                                    this.outline()\n                                } else {\n                                    this.ghost()\n                                }\n                            })\n                            .label(page.to_string())\n                            .compact()\n                            .disabled(is_disabled)\n                            .when(!is_selected, |this| {\n                                this.when_some(on_click.clone(), |this, handler| {\n                                    this.on_click(move |_, window, cx| {\n                                        handler(&page, window, cx);\n                                    })\n                                })\n                            })\n                            .into_any_element()\n                    }\n                    PageItem::Ellipsis(range) => Button::new(SharedString::from(format!(\n                        \"ellipsis-{}-{}\",\n                        range.start, range.end\n                    )))\n                    .ghost()\n                    .with_size(self.size)\n                    .compact()\n                    .disabled(self.disabled)\n                    .icon(IconName::Ellipsis)\n                    .dropdown_menu({\n                        let on_click = on_click.clone();\n                        move |mut menu, _, _| {\n                            for page in range.clone() {\n                                menu = menu.item(\n                                    PopupMenuItem::new(format!(\"{}\", page))\n                                        .checked(page == current_page)\n                                        .on_click({\n                                            let on_click = on_click.clone();\n                                            move |_, window, cx| {\n                                                if let Some(handler) = &on_click {\n                                                    handler(&page, window, cx);\n                                                }\n                                            }\n                                        }),\n                                )\n                            }\n\n                            menu.min_w(px(55.)).max_h(px(240.)).scrollable(true)\n                        }\n                    })\n                    .into_any_element(),\n                })\n            })\n            .child(self.render_nav_button(false))\n    }\n}\n\nfn calculate_page_range(current: usize, total: usize, max_visible: usize) -> Vec<PageItem> {\n    if total <= 1 {\n        return vec![];\n    }\n\n    let max_visible = max_visible.max(5);\n\n    if total <= max_visible {\n        return (1..=total).map(PageItem::Page).collect();\n    }\n\n    let mut pages = vec![];\n    let side_pages = (max_visible - 3) / 2;\n\n    pages.push(PageItem::Page(1));\n\n    let start = if current <= side_pages + 1 {\n        2\n    } else if current > total - side_pages - 1 {\n        total - side_pages - 1\n    } else {\n        current - side_pages\n    };\n\n    if start > 2 {\n        pages.push(PageItem::Ellipsis(2..start));\n    }\n\n    let end = if current >= total - side_pages {\n        total - 1\n    } else if current <= side_pages + 1 {\n        side_pages + 2\n    } else {\n        current + side_pages\n    };\n\n    for page in start..=end {\n        pages.push(PageItem::Page(page));\n    }\n\n    if end < total - 1 {\n        pages.push(PageItem::Ellipsis(end + 1..total));\n    }\n\n    pages.push(PageItem::Page(total));\n\n    pages\n}\n\n#[cfg(test)]\nmod tests {\n    #[test]\n    fn test_calculate_page_range() {\n        use super::{PageItem, calculate_page_range};\n\n        let result = calculate_page_range(1, 10, 7);\n        let expected = vec![\n            PageItem::Page(1),\n            PageItem::Page(2),\n            PageItem::Page(3),\n            PageItem::Page(4),\n            PageItem::Ellipsis(5..10),\n            PageItem::Page(10),\n        ];\n        assert_eq!(result, expected);\n\n        let result = calculate_page_range(5, 10, 7);\n        let expected = vec![\n            PageItem::Page(1),\n            PageItem::Ellipsis(2..3),\n            PageItem::Page(3),\n            PageItem::Page(4),\n            PageItem::Page(5),\n            PageItem::Page(6),\n            PageItem::Page(7),\n            PageItem::Ellipsis(8..10),\n            PageItem::Page(10),\n        ];\n        assert_eq!(result, expected);\n\n        let result = calculate_page_range(10, 10, 7);\n        let expected = vec![\n            PageItem::Page(1),\n            PageItem::Ellipsis(2..7),\n            PageItem::Page(7),\n            PageItem::Page(8),\n            PageItem::Page(9),\n            PageItem::Page(10),\n        ];\n        assert_eq!(result, expected);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/axis.rs",
    "content": "use gpui::{\n    App, Bounds, FontWeight, Hsla, PathBuilder, Pixels, Point, SharedString, TextAlign, Window,\n    point, px,\n};\n\nuse super::{label::PlotLabel, label::TEXT_GAP, label::TEXT_SIZE, label::Text, origin_point};\n\npub const AXIS_GAP: f32 = 18.;\n\npub struct AxisText {\n    pub text: SharedString,\n    pub tick: Pixels,\n    pub color: Hsla,\n    pub font_size: Pixels,\n    pub align: TextAlign,\n}\n\nimpl AxisText {\n    pub fn new(text: impl Into<SharedString>, tick: impl Into<Pixels>, color: Hsla) -> Self {\n        Self {\n            text: text.into(),\n            tick: tick.into(),\n            color,\n            font_size: TEXT_SIZE.into(),\n            align: TextAlign::Left,\n        }\n    }\n\n    pub fn font_size(mut self, font_size: impl Into<Pixels>) -> Self {\n        self.font_size = font_size.into();\n        self\n    }\n\n    pub fn align(mut self, align: TextAlign) -> Self {\n        self.align = align;\n        self\n    }\n}\n\n#[derive(Default)]\npub struct PlotAxis {\n    x: Option<Pixels>,\n    x_label: PlotLabel,\n    x_axis: bool,\n    y: Option<Pixels>,\n    y_label: PlotLabel,\n    y_axis: bool,\n    stroke: Hsla,\n}\n\nimpl PlotAxis {\n    pub fn new() -> Self {\n        Self {\n            x_axis: true,\n            ..Default::default()\n        }\n    }\n\n    /// Set the x-axis of the Axis.\n    pub fn x(mut self, x: impl Into<Pixels>) -> Self {\n        self.x = Some(x.into());\n        self\n    }\n\n    /// Show or hide the x-axis of the Axis.\n    ///\n    /// Default is true.\n    pub fn x_axis(mut self, x_axis: bool) -> Self {\n        self.x_axis = x_axis;\n        self\n    }\n\n    /// Set the x-label of the Axis.\n    pub fn x_label(mut self, label: impl IntoIterator<Item = AxisText>) -> Self {\n        if let Some(x) = self.x {\n            self.x_label = label\n                .into_iter()\n                .map(|t| Text {\n                    text: t.text,\n                    origin: point(t.tick, x + px(TEXT_GAP * 3.)),\n                    color: t.color,\n                    font_size: t.font_size,\n                    font_weight: FontWeight::NORMAL,\n                    align: t.align,\n                })\n                .into();\n        }\n        self\n    }\n\n    /// Set the y-axis of the Axis.\n    pub fn y(mut self, y: impl Into<Pixels>) -> Self {\n        self.y = Some(y.into());\n        self\n    }\n\n    /// Show or hide the y-axis of the Axis.\n    ///\n    /// Default is true.\n    pub fn y_axis(mut self, y_axis: bool) -> Self {\n        self.y_axis = y_axis;\n        self\n    }\n\n    /// Set the y-label of the Axis.\n    pub fn y_label(mut self, label: impl IntoIterator<Item = AxisText>) -> Self {\n        if let Some(y) = self.y {\n            self.y_label = label\n                .into_iter()\n                .map(|t| Text {\n                    text: t.text,\n                    origin: point(y + px(TEXT_GAP), t.tick),\n                    color: t.color,\n                    font_size: t.font_size,\n                    font_weight: FontWeight::NORMAL,\n                    align: t.align,\n                })\n                .into();\n        }\n        self\n    }\n\n    /// Set the stroke color of the Axis.\n    pub fn stroke(mut self, stroke: impl Into<Hsla>) -> Self {\n        self.stroke = stroke.into();\n        self\n    }\n\n    fn draw_axis(&self, start_point: Point<Pixels>, end_point: Point<Pixels>, window: &mut Window) {\n        let mut builder = PathBuilder::stroke(px(1.));\n        builder.move_to(start_point);\n        builder.line_to(end_point);\n        if let Ok(path) = builder.build() {\n            window.paint_path(path, self.stroke);\n        }\n    }\n\n    /// Paint the Axis.\n    pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window, cx: &mut App) {\n        let origin = bounds.origin;\n\n        // X axis\n        if let Some(x) = self.x {\n            if self.x_axis {\n                self.draw_axis(\n                    origin_point(px(0.), x, origin),\n                    origin_point(bounds.size.width, x, origin),\n                    window,\n                );\n            }\n        }\n        self.x_label.paint(bounds, window, cx);\n\n        // Y axis\n        if let Some(y) = self.y {\n            if self.y_axis {\n                self.draw_axis(\n                    origin_point(y, px(0.), origin),\n                    origin_point(y, bounds.size.height, origin),\n                    window,\n                );\n            }\n        }\n        self.y_label.paint(bounds, window, cx);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/grid.rs",
    "content": "use gpui::{px, Bounds, Hsla, PathBuilder, Pixels, Point, Window};\n\nuse super::origin_point;\n\npub struct Grid {\n    x: Vec<Pixels>,\n    y: Vec<Pixels>,\n    stroke: Hsla,\n    dash_array: Option<Vec<Pixels>>,\n}\n\nimpl Grid {\n    #[allow(clippy::new_without_default)]\n    pub fn new() -> Self {\n        Self {\n            x: vec![],\n            y: vec![],\n            stroke: Default::default(),\n            dash_array: None,\n        }\n    }\n\n    /// Set the x of the Grid.\n    pub fn x(mut self, x: Vec<impl Into<Pixels>>) -> Self {\n        self.x = x.into_iter().map(|v| v.into()).collect();\n        self\n    }\n\n    /// Set the y of the Grid.\n    pub fn y(mut self, y: Vec<impl Into<Pixels>>) -> Self {\n        self.y = y.into_iter().map(|v| v.into()).collect();\n        self\n    }\n\n    /// Set the stroke color of the Grid.\n    pub fn stroke(mut self, stroke: impl Into<Hsla>) -> Self {\n        self.stroke = stroke.into();\n        self\n    }\n\n    /// Set the dash array of the Grid.\n    pub fn dash_array(mut self, dash_array: &[Pixels]) -> Self {\n        self.dash_array = Some(dash_array.to_vec());\n        self\n    }\n\n    fn points(&self, bounds: &Bounds<Pixels>) -> Vec<(Point<Pixels>, Point<Pixels>)> {\n        let size = bounds.size;\n        let origin = bounds.origin;\n\n        let mut x = self\n            .x\n            .iter()\n            .map(|x| {\n                (\n                    origin_point(*x, px(0.), origin),\n                    origin_point(*x, size.height, origin),\n                )\n            })\n            .collect::<Vec<_>>();\n\n        let y = self\n            .y\n            .iter()\n            .map(|y| {\n                (\n                    origin_point(px(0.), *y, origin),\n                    origin_point(size.width, *y, origin),\n                )\n            })\n            .collect::<Vec<_>>();\n\n        x.extend(y);\n        x\n    }\n\n    /// Paint the Grid.\n    pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window) {\n        let points = self.points(bounds);\n\n        for (start, end) in points {\n            let mut builder = PathBuilder::stroke(px(1.));\n\n            if let Some(dash_array) = &self.dash_array {\n                builder = builder.dash_array(&dash_array);\n            }\n\n            builder.move_to(start);\n            builder.line_to(end);\n            if let Ok(line) = builder.build() {\n                window.paint_path(line, self.stroke);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/label.rs",
    "content": "use std::fmt::Debug;\n\nuse gpui::{\n    point, px, App, Bounds, FontWeight, Hsla, Pixels, Point, SharedString, TextAlign, TextRun,\n    Window,\n};\n\nuse super::origin_point;\n\npub const TEXT_SIZE: f32 = 10.;\npub const TEXT_GAP: f32 = 2.;\npub const TEXT_HEIGHT: f32 = TEXT_SIZE + TEXT_GAP;\n\npub struct Text {\n    pub text: SharedString,\n    pub origin: Point<Pixels>,\n    pub color: Hsla,\n    pub font_size: Pixels,\n    pub font_weight: FontWeight,\n    pub align: TextAlign,\n}\n\nimpl Text {\n    pub fn new<T>(text: impl Into<SharedString>, origin: Point<T>, color: Hsla) -> Self\n    where\n        T: Default + Clone + Copy + Debug + PartialEq + Into<Pixels>,\n    {\n        let origin = point(origin.x.into(), origin.y.into());\n\n        Self {\n            text: text.into(),\n            origin,\n            color,\n            font_size: TEXT_SIZE.into(),\n            font_weight: FontWeight::NORMAL,\n            align: TextAlign::Left,\n        }\n    }\n\n    /// Set the font size of the Text.\n    pub fn font_size(mut self, font_size: impl Into<Pixels>) -> Self {\n        self.font_size = font_size.into();\n        self\n    }\n\n    /// Set the font weight of the Text.\n    pub fn font_weight(mut self, font_weight: FontWeight) -> Self {\n        self.font_weight = font_weight;\n        self\n    }\n\n    /// Set the alignment of the Text.\n    pub fn align(mut self, align: TextAlign) -> Self {\n        self.align = align;\n        self\n    }\n}\n\nimpl<I> From<I> for PlotLabel\nwhere\n    I: Iterator<Item = Text>,\n{\n    fn from(items: I) -> Self {\n        Self::new(items.collect())\n    }\n}\n\n#[derive(Default)]\npub struct PlotLabel(Vec<Text>);\n\nimpl PlotLabel {\n    pub fn new(items: Vec<Text>) -> Self {\n        Self(items)\n    }\n\n    /// Paint the Label.\n    pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window, cx: &mut App) {\n        for Text {\n            text,\n            origin,\n            color,\n            font_size,\n            font_weight,\n            align,\n        } in self.0.iter()\n        {\n            let origin = origin_point(origin.x, origin.y, bounds.origin);\n\n            let text_run = TextRun {\n                len: text.len(),\n                font: window.text_style().highlight(*font_weight).font(),\n                color: *color,\n                background_color: None,\n                underline: None,\n                strikethrough: None,\n            };\n\n            if let Ok(text) =\n                window\n                    .text_system()\n                    .shape_text(text.clone(), *font_size, &[text_run], None, None)\n            {\n                for line in text {\n                    let origin = match align {\n                        TextAlign::Left => origin,\n                        TextAlign::Right => origin - point(line.size(*font_size).width, px(0.)),\n                        _ => origin - point(line.size(*font_size).width / 2., px(0.)),\n                    };\n\n                    let _ = line.paint(origin, *font_size, *align, None, window, cx);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/mod.rs",
    "content": "mod axis;\nmod grid;\npub mod label;\npub mod scale;\npub mod shape;\npub mod tooltip;\n\npub use gpui_component_macros::IntoPlot;\n\nuse std::{fmt::Debug, ops::Add};\n\nuse gpui::{App, Bounds, IntoElement, Path, PathBuilder, Pixels, Point, Window, point, px};\n\npub use axis::{AXIS_GAP, AxisText, PlotAxis};\npub use grid::Grid;\npub use label::PlotLabel;\n\npub trait Plot: IntoElement {\n    fn paint(&mut self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App);\n}\n\n#[derive(Clone, Copy, Default)]\npub enum StrokeStyle {\n    #[default]\n    Natural,\n    Linear,\n    StepAfter,\n}\n\npub fn origin_point<T>(x: T, y: T, origin: Point<T>) -> Point<T>\nwhere\n    T: Default + Clone + Debug + PartialEq + Add<Output = T>,\n{\n    point(x, y) + origin\n}\n\npub fn polygon<T>(points: &[Point<T>], bounds: &Bounds<Pixels>) -> Option<Path<Pixels>>\nwhere\n    T: Default + Clone + Copy + Debug + Into<f32> + PartialEq,\n{\n    let mut path = PathBuilder::stroke(px(1.));\n    let points = &points\n        .iter()\n        .map(|p| {\n            point(\n                px(p.x.into() + bounds.origin.x.as_f32()),\n                px(p.y.into() + bounds.origin.y.as_f32()),\n            )\n        })\n        .collect::<Vec<_>>();\n    path.add_polygon(points, false);\n    path.build().ok()\n}\n"
  },
  {
    "path": "crates/ui/src/plot/scale/band.rs",
    "content": "// @reference: https://d3js.org/d3-scale/band\n\nuse itertools::Itertools;\nuse num_traits::Zero;\n\nuse super::Scale;\n\n#[derive(Clone)]\npub struct ScaleBand<T> {\n    domain: Vec<T>,\n    range_diff: f32,\n    avg_width: f32,\n    padding_inner: f32,\n    padding_outer: f32,\n}\n\nimpl<T> ScaleBand<T> {\n    pub fn new(domain: Vec<T>, range: Vec<f32>) -> Self {\n        let len = domain.len() as f32;\n        let range_diff = range\n            .iter()\n            .minmax()\n            .into_option()\n            .map_or(0., |(min, max)| max - min);\n\n        Self {\n            domain,\n            range_diff,\n            avg_width: if len.is_zero() { 0. } else { range_diff / len },\n            padding_inner: 0.,\n            padding_outer: 0.,\n        }\n    }\n\n    /// Get the width of the band.\n    pub fn band_width(&self) -> f32 {\n        (self.avg_width * (1. - self.padding_inner)).min(30.)\n    }\n\n    /// Set the padding inner of the band.\n    pub fn padding_inner(mut self, padding_inner: f32) -> Self {\n        self.padding_inner = padding_inner;\n        self\n    }\n\n    /// Set the padding outer of the band.\n    pub fn padding_outer(mut self, padding_outer: f32) -> Self {\n        self.padding_outer = padding_outer;\n        self\n    }\n\n    /// Get the ratio of the band.\n    fn ratio(&self) -> f32 {\n        1. + self.padding_inner / (self.domain.len() - 1) as f32\n    }\n\n    /// Get the average width of the band for display.\n    fn display_avg_width(&self) -> f32 {\n        let padding_outer_width = self.avg_width * self.padding_outer;\n        (self.range_diff - padding_outer_width * 2.) / self.domain.len() as f32\n    }\n}\n\nimpl<T> Scale<T> for ScaleBand<T>\nwhere\n    T: PartialEq,\n{\n    fn tick(&self, value: &T) -> Option<f32> {\n        let index = self.domain.iter().position(|v| v == value)?;\n        let domain_len = self.domain.len();\n\n        // When there's only one element, place it in the center.\n        if domain_len == 1 {\n            return Some((self.range_diff - self.band_width()) / 2.);\n        }\n\n        let avg_width = self.display_avg_width();\n        let padding_outer_width = self.avg_width * self.padding_outer;\n        Some(index as f32 * avg_width * self.ratio() + padding_outer_width)\n    }\n\n    fn least_index(&self, tick: f32) -> usize {\n        if self.domain.is_empty() {\n            return 0;\n        }\n\n        let domain_len = self.domain.len();\n\n        // Handle single element case\n        if domain_len == 1 {\n            return 0;\n        }\n\n        let avg_width = self.display_avg_width();\n        let padding_outer_width = self.avg_width * self.padding_outer;\n        let adjusted_tick = tick - padding_outer_width;\n        let index = (adjusted_tick / (avg_width * self.ratio())).round() as i32;\n\n        (index.max(0) as usize).min(domain_len.saturating_sub(1))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_scale_band() {\n        let scale = ScaleBand::new(vec![1, 2, 3], vec![0., 90.]);\n        assert_eq!(scale.tick(&1), Some(0.));\n        assert_eq!(scale.tick(&2), Some(30.));\n        assert_eq!(scale.tick(&3), Some(60.));\n        assert_eq!(scale.band_width(), 30.);\n    }\n\n    #[test]\n    fn test_scale_band_zero() {\n        let scale = ScaleBand::new(vec![], vec![0., 90.]);\n        assert_eq!(scale.tick(&1), None);\n        assert_eq!(scale.tick(&2), None);\n        assert_eq!(scale.tick(&3), None);\n        assert_eq!(scale.band_width(), 0.);\n\n        let scale = ScaleBand::new(vec![1, 2, 3], vec![]);\n        assert_eq!(scale.tick(&1), Some(0.));\n        assert_eq!(scale.tick(&2), Some(0.));\n        assert_eq!(scale.tick(&3), Some(0.));\n        assert_eq!(scale.band_width(), 0.);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/scale/linear.rs",
    "content": "// @reference: https://d3js.org/d3-scale/linear\n\nuse itertools::Itertools;\nuse num_traits::{Num, ToPrimitive};\n\nuse super::{sealed::Sealed, Scale};\n\n#[derive(Clone)]\npub struct ScaleLinear<T> {\n    domain_len: usize,\n    domain_start: T,\n    domain_diff: T,\n    range_start: f32,\n    range_diff: f32,\n}\n\nimpl<T> ScaleLinear<T>\nwhere\n    T: Copy + PartialOrd + Num + ToPrimitive + Sealed,\n{\n    pub fn new(domain: Vec<T>, range: Vec<f32>) -> Self {\n        let (domain_start, domain_end) = domain\n            .iter()\n            .minmax()\n            .into_option()\n            .map_or((T::zero(), T::zero()), |(min, max)| (*min, *max));\n\n        let (range_start, range_end) =\n            range\n                .iter()\n                .minmax()\n                .into_option()\n                .map_or((0., 0.), |(min, max)| {\n                    let min_pos = range.iter().position(|&x| x == *min).unwrap_or(0);\n                    let max_pos = range.iter().position(|&x| x == *max).unwrap_or(0);\n\n                    if min_pos <= max_pos {\n                        (*min, *max)\n                    } else {\n                        (*max, *min)\n                    }\n                });\n\n        Self {\n            domain_len: domain.len(),\n            domain_start,\n            domain_diff: domain_end - domain_start,\n            range_start,\n            range_diff: range_end - range_start,\n        }\n    }\n}\n\nimpl<T> Scale<T> for ScaleLinear<T>\nwhere\n    T: Copy + PartialOrd + Num + ToPrimitive + Sealed,\n{\n    fn tick(&self, value: &T) -> Option<f32> {\n        if self.domain_diff.is_zero() {\n            return None;\n        }\n\n        let ratio = ((*value - self.domain_start) / self.domain_diff).to_f32()?;\n\n        Some(ratio * self.range_diff + self.range_start)\n    }\n\n    fn least_index_with_domain(&self, tick: f32, domain: &[T]) -> (usize, f32) {\n        if self.domain_len == 0 || domain.is_empty() {\n            return (0, 0.);\n        }\n\n        domain\n            .iter()\n            .flat_map(|v| self.tick(v))\n            .enumerate()\n            .min_by(|(_, a), (_, b)| {\n                ((*a) - tick)\n                    .abs()\n                    .partial_cmp(&((*b) - tick).abs())\n                    .unwrap_or(std::cmp::Ordering::Equal)\n            })\n            .unwrap_or((0, 0.))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_scale_linear() {\n        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![0., 100.]);\n        assert_eq!(scale.tick(&1.), Some(0.));\n        assert_eq!(scale.tick(&2.), Some(50.));\n        assert_eq!(scale.tick(&3.), Some(100.));\n\n        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![100., 0.]);\n        assert_eq!(scale.tick(&1.), Some(100.));\n        assert_eq!(scale.tick(&2.), Some(50.));\n        assert_eq!(scale.tick(&3.), Some(0.));\n    }\n\n    #[test]\n    fn test_scale_linear_multiple_range() {\n        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![0., 50., 100.]);\n        assert_eq!(scale.tick(&1.), Some(0.));\n        assert_eq!(scale.tick(&2.), Some(50.));\n        assert_eq!(scale.tick(&3.), Some(100.));\n\n        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![100., 50., 0.]);\n        assert_eq!(scale.tick(&1.), Some(100.));\n        assert_eq!(scale.tick(&2.), Some(50.));\n        assert_eq!(scale.tick(&3.), Some(0.));\n\n        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![100., 0., 100.]);\n        assert_eq!(scale.tick(&1.), Some(100.));\n        assert_eq!(scale.tick(&2.), Some(50.));\n        assert_eq!(scale.tick(&3.), Some(0.));\n\n        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![0., 100., 0.]);\n        assert_eq!(scale.tick(&1.), Some(0.));\n        assert_eq!(scale.tick(&2.), Some(50.));\n        assert_eq!(scale.tick(&3.), Some(100.));\n    }\n\n    #[test]\n    fn test_scale_linear_empty() {\n        let scale = ScaleLinear::new(vec![], vec![0., 100.]);\n        assert_eq!(scale.tick(&1.), None);\n        assert_eq!(scale.tick(&2.), None);\n        assert_eq!(scale.tick(&3.), None);\n\n        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![]);\n        assert_eq!(scale.tick(&1.), Some(0.));\n        assert_eq!(scale.tick(&2.), Some(0.));\n        assert_eq!(scale.tick(&3.), Some(0.));\n    }\n\n    #[test]\n    fn test_scale_linear_least_index_with_domain() {\n        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![0., 100.]);\n        assert_eq!(scale.least_index_with_domain(0., &[1., 2., 3.]), (0, 0.));\n        assert_eq!(scale.least_index_with_domain(50., &[1., 2., 3.]), (1, 50.));\n        assert_eq!(\n            scale.least_index_with_domain(100., &[1., 2., 3.]),\n            (2, 100.)\n        );\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/scale/ordinal.rs",
    "content": "// @reference: https://d3js.org/d3-scale/ordinal\n\n#[derive(Clone)]\npub struct ScaleOrdinal<D, R> {\n    domain: Vec<D>,\n    range: Vec<R>,\n    unknown: Option<R>,\n}\n\nimpl<D, R> Default for ScaleOrdinal<D, R> {\n    fn default() -> Self {\n        Self {\n            domain: Vec::new(),\n            range: Vec::new(),\n            unknown: None,\n        }\n    }\n}\n\nimpl<D, R> ScaleOrdinal<D, R> {\n    pub fn new(domain: Vec<D>, range: Vec<R>) -> Self {\n        Self {\n            domain,\n            range,\n            unknown: None,\n        }\n    }\n\n    /// Set the domain to the specified array of values.\n    pub fn domain(mut self, domain: Vec<D>) -> Self {\n        self.domain = domain;\n        self\n    }\n\n    /// Set the range of the ordinal scale to the specified array of values.\n    pub fn range(mut self, range: Vec<R>) -> Self {\n        self.range = range;\n        self\n    }\n\n    /// Set the output value of the scale for unknown input values and returns this scale.\n    pub fn unknown(mut self, unknown: R) -> Self {\n        self.unknown = Some(unknown);\n        self\n    }\n}\n\nimpl<D, R> ScaleOrdinal<D, R>\nwhere\n    D: PartialEq,\n    R: Clone,\n{\n    /// Given a value in the input domain, returns the corresponding value in the output range.\n    pub fn map(&self, value: &D) -> Option<R> {\n        if let Some(index) = self.domain.iter().position(|v| v == value) {\n            if self.range.is_empty() {\n                None\n            } else {\n                Some(self.range[index % self.range.len()].clone())\n            }\n        } else {\n            self.unknown.clone()\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_scale_ordinal() {\n        let scale = ScaleOrdinal::new(vec![\"a\", \"b\", \"c\", \"d\", \"e\"], vec![10, 20, 30]);\n\n        assert_eq!(scale.map(&\"a\"), Some(10));\n        assert_eq!(scale.map(&\"b\"), Some(20));\n        assert_eq!(scale.map(&\"c\"), Some(30));\n        assert_eq!(scale.map(&\"d\"), Some(10));\n        assert_eq!(scale.map(&\"e\"), Some(20));\n        assert_eq!(scale.map(&\"f\"), None);\n    }\n\n    #[test]\n    fn test_scale_ordinal_unknown() {\n        let scale = ScaleOrdinal::new(vec![\"a\", \"b\", \"c\"], vec![10, 20, 30]).unknown(0);\n\n        assert_eq!(scale.map(&\"a\"), Some(10));\n        assert_eq!(scale.map(&\"f\"), Some(0));\n    }\n\n    #[test]\n    fn test_scale_ordinal_colors() {\n        let keys = vec![\"a\", \"b\", \"c\", \"d\"];\n        let colors = vec![\"#1f77b4\", \"#ff7f0e\", \"#2ca02c\"];\n\n        let scale = ScaleOrdinal::new(keys, colors);\n\n        assert_eq!(scale.map(&\"a\"), Some(\"#1f77b4\"));\n        assert_eq!(scale.map(&\"b\"), Some(\"#ff7f0e\"));\n        assert_eq!(scale.map(&\"c\"), Some(\"#2ca02c\"));\n        // Should cycle back to the first color\n        assert_eq!(scale.map(&\"d\"), Some(\"#1f77b4\"));\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/scale/point.rs",
    "content": "// @reference: https://d3js.org/d3-scale/point\n\nuse itertools::Itertools;\n\nuse super::Scale;\n\n/// Point scale maps discrete domain values to continuous range positions.\n///\n/// Points are evenly distributed across the range, with the first and last points\n/// aligned to the range boundaries.\n#[derive(Clone)]\npub struct ScalePoint<T> {\n    domain: Vec<T>,\n    range_start: f32,\n    range_tick: f32,\n}\n\nimpl<T> ScalePoint<T>\nwhere\n    T: PartialEq,\n{\n    /// Creates a new point scale with the given domain and range.\n    ///\n    /// # Examples\n    ///\n    /// ```ignore\n    /// let scale = ScalePoint::new(vec![1, 2, 3], vec![0., 100.]);\n    /// assert_eq!(scale.tick(&1), Some(0.));\n    /// assert_eq!(scale.tick(&2), Some(50.));\n    /// assert_eq!(scale.tick(&3), Some(100.));\n    /// ```\n    pub fn new(domain: Vec<T>, range: Vec<f32>) -> Self {\n        let len = domain.len();\n        let (range_start, range_tick) = if len == 0 {\n            (0., 0.)\n        } else {\n            let (min, max) = range\n                .iter()\n                .minmax()\n                .into_option()\n                .map_or((0., 0.), |(min, max)| (*min, *max));\n\n            let range_diff = max - min;\n\n            if len == 1 {\n                (min, range_diff)\n            } else {\n                (min, range_diff / (len - 1) as f32)\n            }\n        };\n\n        Self {\n            domain,\n            range_start,\n            range_tick,\n        }\n    }\n}\n\nimpl<T> Scale<T> for ScalePoint<T>\nwhere\n    T: PartialEq,\n{\n    fn tick(&self, value: &T) -> Option<f32> {\n        let index = self.domain.iter().position(|v| v == value)?;\n\n        if self.domain.len() == 1 {\n            Some(self.range_start + self.range_tick / 2.)\n        } else {\n            Some(self.range_start + index as f32 * self.range_tick)\n        }\n    }\n\n    fn least_index(&self, tick: f32) -> usize {\n        if self.domain.is_empty() {\n            return 0;\n        }\n\n        if self.range_tick == 0. {\n            return 0;\n        }\n\n        let normalized_tick = tick - self.range_start;\n        let index = (normalized_tick / self.range_tick).round() as usize;\n        index.min(self.domain.len() - 1)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_scale_point() {\n        let scale = ScalePoint::new(vec![1, 2, 3], vec![0., 100.]);\n        assert_eq!(scale.tick(&1), Some(0.));\n        assert_eq!(scale.tick(&2), Some(50.));\n        assert_eq!(scale.tick(&3), Some(100.));\n    }\n\n    #[test]\n    fn test_scale_point_range() {\n        let scale = ScalePoint::new(vec![1, 2, 3], vec![40., 80.]);\n        assert_eq!(scale.tick(&1), Some(40.));\n        assert_eq!(scale.tick(&2), Some(60.));\n        assert_eq!(scale.tick(&3), Some(80.));\n    }\n\n    #[test]\n    fn test_scale_point_empty() {\n        let scale = ScalePoint::new(vec![], vec![0., 100.]);\n        assert_eq!(scale.tick(&1), None);\n        assert_eq!(scale.tick(&2), None);\n        assert_eq!(scale.tick(&3), None);\n\n        let scale = ScalePoint::new(vec![1, 2, 3], vec![]);\n        assert_eq!(scale.tick(&1), Some(0.));\n        assert_eq!(scale.tick(&2), Some(0.));\n        assert_eq!(scale.tick(&3), Some(0.));\n    }\n\n    #[test]\n    fn test_scale_point_single() {\n        let scale = ScalePoint::new(vec![1], vec![0., 100.]);\n        assert_eq!(scale.tick(&1), Some(50.));\n    }\n\n    #[test]\n    fn test_least_index_basic() {\n        let scale = ScalePoint::new(vec![1, 2, 3], vec![0., 100.]);\n\n        // Exact positions\n        assert_eq!(scale.least_index(0.), 0);\n        assert_eq!(scale.least_index(50.), 1);\n        assert_eq!(scale.least_index(100.), 2);\n\n        // Between positions (should round to nearest)\n        assert_eq!(scale.least_index(24.), 0); // closer to 0\n        assert_eq!(scale.least_index(25.), 1); // equidistant, rounds to 1\n        assert_eq!(scale.least_index(26.), 1); // closer to 50\n        assert_eq!(scale.least_index(74.), 1); // closer to 50\n        assert_eq!(scale.least_index(75.), 2); // equidistant, rounds to 2\n        assert_eq!(scale.least_index(76.), 2); // closer to 100\n\n        // Outside range\n        assert_eq!(scale.least_index(-10.), 0); // below min\n        assert_eq!(scale.least_index(150.), 2); // above max\n    }\n\n    #[test]\n    fn test_least_index_with_offset() {\n        let scale = ScalePoint::new(vec![1, 2, 3], vec![40., 80.]);\n\n        // Exact positions: 40, 60, 80\n        assert_eq!(scale.least_index(40.), 0);\n        assert_eq!(scale.least_index(60.), 1);\n        assert_eq!(scale.least_index(80.), 2);\n\n        // Between positions\n        assert_eq!(scale.least_index(49.), 0); // closer to 40\n        assert_eq!(scale.least_index(50.), 1); // equidistant, rounds to 1\n        assert_eq!(scale.least_index(51.), 1); // closer to 60\n        assert_eq!(scale.least_index(69.), 1); // closer to 60\n        assert_eq!(scale.least_index(70.), 2); // equidistant, rounds to 2\n        assert_eq!(scale.least_index(71.), 2); // closer to 80\n\n        // Outside range\n        assert_eq!(scale.least_index(30.), 0); // below min\n        assert_eq!(scale.least_index(100.), 2); // above max\n    }\n\n    #[test]\n    fn test_least_index_empty() {\n        let scale = ScalePoint::new(Vec::<i32>::new(), vec![0., 100.]);\n        assert_eq!(scale.least_index(0.), 0);\n        assert_eq!(scale.least_index(50.), 0);\n        assert_eq!(scale.least_index(100.), 0);\n    }\n\n    #[test]\n    fn test_least_index_single() {\n        let scale = ScalePoint::new(vec![1], vec![0., 100.]);\n        assert_eq!(scale.least_index(0.), 0);\n        assert_eq!(scale.least_index(50.), 0);\n        assert_eq!(scale.least_index(100.), 0);\n    }\n\n    #[test]\n    fn test_least_index_empty_range() {\n        let scale = ScalePoint::new(vec![1, 2, 3], vec![]);\n        assert_eq!(scale.least_index(0.), 0);\n        assert_eq!(scale.least_index(50.), 0);\n        assert_eq!(scale.least_index(100.), 0);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/scale/sealed.rs",
    "content": "pub trait Sealed {}\n\nimpl Sealed for f64 {}\n\n#[cfg(feature = \"decimal\")]\nimpl Sealed for rust_decimal::Decimal {}\n"
  },
  {
    "path": "crates/ui/src/plot/scale.rs",
    "content": "mod band;\nmod linear;\nmod ordinal;\nmod point;\nmod sealed;\n\npub use band::ScaleBand;\npub use linear::ScaleLinear;\npub use ordinal::ScaleOrdinal;\npub use point::ScalePoint;\npub(crate) use sealed::Sealed;\n\npub trait Scale<T> {\n    /// Get the tick of the scale.\n    fn tick(&self, value: &T) -> Option<f32>;\n\n    /// Get the least index of the scale.\n    fn least_index(&self, _tick: f32) -> usize {\n        0\n    }\n\n    /// Get the least index of the scale with the domain.\n    fn least_index_with_domain(&self, _tick: f32, _domain: &[T]) -> (usize, f32) {\n        (0, 0.)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/shape/arc.rs",
    "content": "// @reference: https://d3js.org/d3-shape/arc\n\nuse std::{f32::consts::PI, fmt::Debug};\n\nuse gpui::{Bounds, Hsla, Path, PathBuilder, Pixels, Point, Window, point, px};\n\nconst EPSILON: f32 = 1e-12;\nconst HALF_PI: f32 = PI / 2.;\n\npub struct ArcData<'a, T> {\n    pub data: &'a T,\n    pub index: usize,\n    pub value: f32,\n    pub start_angle: f32,\n    pub end_angle: f32,\n    pub pad_angle: f32,\n}\n\nimpl<T> Debug for ArcData<'_, T> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"ArcData {{ index: {}, value: {}, start_angle: {}, end_angle: {}, pad_angle: {} }}\",\n            self.index, self.value, self.start_angle, self.end_angle, self.pad_angle\n        )\n    }\n}\n\npub struct Arc {\n    inner_radius: f32,\n    outer_radius: f32,\n}\n\nimpl Default for Arc {\n    fn default() -> Self {\n        Self {\n            inner_radius: 0.,\n            outer_radius: 0.,\n        }\n    }\n}\n\nimpl Arc {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the inner radius of the Arc.\n    pub fn inner_radius(mut self, inner_radius: f32) -> Self {\n        self.inner_radius = inner_radius;\n        self\n    }\n\n    /// Set the outer radius of the Arc.\n    pub fn outer_radius(mut self, outer_radius: f32) -> Self {\n        self.outer_radius = outer_radius;\n        self\n    }\n\n    /// Get the centroid of the Arc.\n    pub fn centroid<T>(&self, arc: &ArcData<T>) -> Point<f32> {\n        let start_angle = arc.start_angle - HALF_PI;\n        let end_angle = arc.end_angle - HALF_PI;\n        let r = (self.inner_radius + self.outer_radius) / 2.;\n        let a = (start_angle + end_angle) / 2.;\n\n        point(r * a.cos(), r * a.sin())\n    }\n\n    fn path<T>(\n        &self,\n        arc: &ArcData<T>,\n        inner_radius: Option<f32>,\n        outer_radius: Option<f32>,\n        bounds: &Bounds<Pixels>,\n    ) -> Option<Path<Pixels>> {\n        let start_angle = arc.start_angle - HALF_PI;\n        let end_angle = arc.end_angle - HALF_PI;\n        let da = end_angle - start_angle;\n        let pad_angle = if da >= PI {\n            // Leave some pad angle for full circle.\n            // If not, the path start and end will be the same point.\n            0.0001\n        } else {\n            arc.pad_angle\n        };\n        let r0 = inner_radius.unwrap_or(self.inner_radius).max(0.);\n        let r1 = outer_radius.unwrap_or(self.outer_radius).max(0.);\n\n        // Calculate the center point.\n        let center_x = bounds.origin.x.as_f32() + bounds.size.width.as_f32() / 2.;\n        let center_y = bounds.origin.y.as_f32() + bounds.size.height.as_f32() / 2.;\n\n        // Angle difference.\n        if r1 < EPSILON || da.abs() < EPSILON {\n            return None;\n        }\n\n        // Handle pad angle.\n        let (a0_outer, a1_outer, a0_inner, a1_inner) = if r0 > EPSILON && pad_angle > 0.0 {\n            let pad_width = r1 * pad_angle;\n            let pad_angle_outer = pad_width / r1;\n            let mut pad_angle_inner = pad_width / r0;\n            let max_inner_pad = da * 0.8;\n            if pad_angle_inner > max_inner_pad {\n                pad_angle_inner = max_inner_pad;\n            }\n            (\n                start_angle + pad_angle_outer * 0.5,\n                end_angle - pad_angle_outer * 0.5,\n                start_angle + pad_angle_inner * 0.5,\n                end_angle - pad_angle_inner * 0.5,\n            )\n        } else {\n            let pad = pad_angle * 0.5;\n            (\n                start_angle + pad,\n                end_angle - pad,\n                start_angle + pad,\n                end_angle - pad,\n            )\n        };\n\n        let da_outer = a1_outer - a0_outer;\n        if da_outer <= 0. {\n            return None;\n        }\n\n        // Calculate the start and end points of the outer arc.\n        let x01 = center_x + r1 * a0_outer.cos();\n        let y01 = center_y + r1 * a0_outer.sin();\n        let x11 = center_x + r1 * a1_outer.cos();\n        let y11 = center_y + r1 * a1_outer.sin();\n\n        let mut builder = PathBuilder::fill();\n\n        // Move to the start point of the outer arc.\n        builder.move_to(point(px(x01), px(y01)));\n\n        // Draw the outer arc.\n        let large_arc = (a1_outer - a0_outer).abs() > PI;\n        builder.arc_to(\n            point(px(r1), px(r1)),\n            px(0.),\n            large_arc,\n            true,\n            point(px(x11), px(y11)),\n        );\n\n        if r0 > EPSILON {\n            // End point of the inner arc.\n            let x10 = center_x + r0 * a1_inner.cos();\n            let y10 = center_y + r0 * a1_inner.sin();\n            builder.line_to(point(px(x10), px(y10)));\n\n            // Draw the inner arc.\n            let x00 = center_x + r0 * a0_inner.cos();\n            let y00 = center_y + r0 * a0_inner.sin();\n            let large_arc_inner = (a1_inner - a0_inner).abs() > PI;\n            builder.arc_to(\n                point(px(r0), px(r0)),\n                px(0.),\n                large_arc_inner,\n                false,\n                point(px(x00), px(y00)),\n            );\n        } else {\n            // If there is no inner radius, draw a line to the center.\n            builder.line_to(point(px(center_x), px(center_y)));\n        }\n\n        builder.build().ok()\n    }\n\n    /// Paint the Arc.\n    pub fn paint<T>(\n        &self,\n        arc: &ArcData<T>,\n        color: impl Into<Hsla>,\n        inner_radius: Option<f32>,\n        outer_radius: Option<f32>,\n        bounds: &Bounds<Pixels>,\n        window: &mut Window,\n    ) {\n        let path = self.path(arc, inner_radius, outer_radius, bounds);\n        if let Some(path) = path {\n            window.paint_path(path, color.into());\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_arc_default() {\n        let arc = Arc::default();\n        assert_eq!(arc.inner_radius, 0.);\n        assert_eq!(arc.outer_radius, 0.);\n    }\n\n    #[test]\n    fn test_arc_builder() {\n        let arc = Arc::new().inner_radius(10.).outer_radius(20.);\n\n        assert_eq!(arc.inner_radius, 10.);\n        assert_eq!(arc.outer_radius, 20.);\n    }\n\n    #[test]\n    fn test_arc_centroid() {\n        let arc = Arc::new().inner_radius(10.).outer_radius(20.);\n\n        let arc_data = ArcData {\n            data: &(),\n            index: 0,\n            value: 1.,\n            start_angle: 0.,\n            end_angle: PI,\n            pad_angle: 0.,\n        };\n\n        let centroid = arc.centroid(&arc_data);\n        let expected_radius = (10. + 20.) / 2.;\n        let expected_angle = (0. + PI - 2. * HALF_PI) / 2.;\n\n        assert_eq!(centroid.x, expected_radius * expected_angle.cos());\n        assert_eq!(centroid.y, expected_radius * expected_angle.sin());\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/shape/area.rs",
    "content": "// @reference: https://d3js.org/d3-shape/area\n\nuse gpui::{Background, Bounds, Path, PathBuilder, Pixels, Point, Window, px};\n\nuse crate::plot::{StrokeStyle, origin_point};\n\n#[allow(clippy::type_complexity)]\npub struct Area<T> {\n    data: Vec<T>,\n    x: Box<dyn Fn(&T) -> Option<f32>>,\n    y0: Option<f32>,\n    y1: Box<dyn Fn(&T) -> Option<f32>>,\n    fill: Background,\n    stroke: Background,\n    stroke_style: StrokeStyle,\n}\n\nimpl<T> Default for Area<T> {\n    fn default() -> Self {\n        Self {\n            data: Vec::new(),\n            x: Box::new(|_| None),\n            y0: None,\n            y1: Box::new(|_| None),\n            fill: Default::default(),\n            stroke: Default::default(),\n            stroke_style: Default::default(),\n        }\n    }\n}\n\nimpl<T> Area<T> {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the data of the Area.\n    pub fn data<I>(mut self, data: I) -> Self\n    where\n        I: IntoIterator<Item = T>,\n    {\n        self.data = data.into_iter().collect();\n        self\n    }\n\n    /// Set the x of the Area.\n    pub fn x<F>(mut self, x: F) -> Self\n    where\n        F: Fn(&T) -> Option<f32> + 'static,\n    {\n        self.x = Box::new(x);\n        self\n    }\n\n    /// Set the y0 of the Area.\n    pub fn y0(mut self, y0: f32) -> Self {\n        self.y0 = Some(y0);\n        self\n    }\n\n    /// Set the y1 of the Area.\n    pub fn y1<F>(mut self, y1: F) -> Self\n    where\n        F: Fn(&T) -> Option<f32> + 'static,\n    {\n        self.y1 = Box::new(y1);\n        self\n    }\n\n    /// Set the fill color of the Area.\n    pub fn fill(mut self, fill: impl Into<Background>) -> Self {\n        self.fill = fill.into();\n        self\n    }\n\n    /// Set the stroke color of the Area.\n    pub fn stroke(mut self, stroke: impl Into<Background>) -> Self {\n        self.stroke = stroke.into();\n        self\n    }\n\n    /// Set the stroke style of the Area.\n    pub fn stroke_style(mut self, stroke_style: StrokeStyle) -> Self {\n        self.stroke_style = stroke_style;\n        self\n    }\n\n    fn path(&self, bounds: &Bounds<Pixels>) -> (Option<Path<Pixels>>, Option<Path<Pixels>>) {\n        let origin = bounds.origin;\n        let mut area_builder = PathBuilder::fill();\n        let mut line_builder = PathBuilder::stroke(px(1.));\n\n        let mut points = vec![];\n\n        let mut first_x_tick = None;\n        let mut last_x_tick = None;\n        for (index, v) in self.data.iter().enumerate() {\n            if index == 0 {\n                first_x_tick = (self.x)(v);\n            }\n            if index == self.data.len() - 1 {\n                last_x_tick = (self.x)(v);\n            }\n            let x_tick = (self.x)(v);\n            let y_tick = (self.y1)(v);\n\n            if let (Some(x), Some(y)) = (x_tick, y_tick) {\n                let pos = origin_point(px(x), px(y), origin);\n\n                points.push(pos);\n            }\n        }\n\n        if points.is_empty() {\n            return (None, None);\n        }\n\n        if points.len() == 1 {\n            area_builder.move_to(points[0]);\n            line_builder.move_to(points[0]);\n            return (area_builder.build().ok(), line_builder.build().ok());\n        }\n\n        match self.stroke_style {\n            StrokeStyle::Natural => {\n                area_builder.move_to(points[0]);\n                line_builder.move_to(points[0]);\n                let n = points.len();\n                for i in 0..n - 1 {\n                    let p0 = if i == 0 { points[0] } else { points[i - 1] };\n                    let p1 = points[i];\n                    let p2 = points[i + 1];\n                    let p3 = if i + 2 < n {\n                        points[i + 2]\n                    } else {\n                        points[n - 1]\n                    };\n\n                    // Catmull-Rom to Bezier\n                    let c1 = Point::new(p1.x + (p2.x - p0.x) / 6.0, p1.y + (p2.y - p0.y) / 6.0);\n                    let c2 = Point::new(p2.x - (p3.x - p1.x) / 6.0, p2.y - (p3.y - p1.y) / 6.0);\n\n                    area_builder.cubic_bezier_to(p2, c1, c2);\n                    line_builder.cubic_bezier_to(p2, c1, c2);\n                }\n            }\n            StrokeStyle::Linear => {\n                area_builder.move_to(points[0]);\n                line_builder.move_to(points[0]);\n                for p in &points[1..] {\n                    area_builder.line_to(*p);\n                    line_builder.line_to(*p);\n                }\n            }\n            StrokeStyle::StepAfter => {\n                area_builder.move_to(points[0]);\n                line_builder.move_to(points[0]);\n                for (i, p) in points.windows(2).enumerate() {\n                    area_builder.line_to(Point::new(p[1].x, p[0].y));\n                    line_builder.line_to(Point::new(p[1].x, p[0].y));\n                    // Don't draw the vertical line for the last point\n                    if i < points.len() - 2 {\n                        area_builder.line_to(p[1]);\n                        line_builder.line_to(p[1]);\n                    }\n                }\n            }\n        }\n\n        // Close path\n        if let (Some(first), Some(last), Some(y)) = (first_x_tick, last_x_tick, self.y0) {\n            area_builder.line_to(origin_point(px(last), px(y), bounds.origin));\n            area_builder.line_to(origin_point(px(first), px(y), bounds.origin));\n            area_builder.close();\n        }\n\n        (area_builder.build().ok(), line_builder.build().ok())\n    }\n\n    /// Paint the Area.\n    pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window) {\n        let (area, line) = self.path(bounds);\n\n        if let Some(area) = area {\n            window.paint_path(area, self.fill);\n        }\n        if let Some(line) = line {\n            window.paint_path(line, self.stroke);\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/shape/bar.rs",
    "content": "use gpui::{App, Bounds, Hsla, PaintQuad, Pixels, Point, Window, fill, point, px};\n\nuse crate::plot::{\n    label::{PlotLabel, TEXT_GAP, TEXT_HEIGHT, Text},\n    origin_point,\n};\n\n#[allow(clippy::type_complexity)]\npub struct Bar<T> {\n    data: Vec<T>,\n    x: Box<dyn Fn(&T) -> Option<f32>>,\n    band_width: f32,\n    y0: Box<dyn Fn(&T) -> f32>,\n    y1: Box<dyn Fn(&T) -> Option<f32>>,\n    fill: Box<dyn Fn(&T) -> Hsla>,\n    label: Option<Box<dyn Fn(&T, Point<Pixels>) -> Vec<Text>>>,\n}\n\nimpl<T> Default for Bar<T> {\n    fn default() -> Self {\n        Self {\n            data: Vec::new(),\n            x: Box::new(|_| None),\n            band_width: 0.,\n            y0: Box::new(|_| 0.),\n            y1: Box::new(|_| None),\n            fill: Box::new(|_| gpui::black()),\n            label: None,\n        }\n    }\n}\n\nimpl<T> Bar<T> {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the data of the Bar.\n    pub fn data<I>(mut self, data: I) -> Self\n    where\n        I: IntoIterator<Item = T>,\n    {\n        self.data = data.into_iter().collect();\n        self\n    }\n\n    /// Set the x of the Bar.\n    pub fn x<F>(mut self, x: F) -> Self\n    where\n        F: Fn(&T) -> Option<f32> + 'static,\n    {\n        self.x = Box::new(x);\n        self\n    }\n\n    /// Set the band width of the Bar.\n    pub fn band_width(mut self, band_width: f32) -> Self {\n        self.band_width = band_width;\n        self\n    }\n\n    /// Set the y0 of the Bar.\n    pub fn y0<F>(mut self, y: F) -> Self\n    where\n        F: Fn(&T) -> f32 + 'static,\n    {\n        self.y0 = Box::new(y);\n        self\n    }\n\n    /// Set the y1 of the Bar.\n    pub fn y1<F>(mut self, y: F) -> Self\n    where\n        F: Fn(&T) -> Option<f32> + 'static,\n    {\n        self.y1 = Box::new(y);\n        self\n    }\n\n    /// Set the fill color of the Bar.\n    pub fn fill<F, C>(mut self, fill: F) -> Self\n    where\n        F: Fn(&T) -> C + 'static,\n        C: Into<Hsla>,\n    {\n        self.fill = Box::new(move |v| fill(v).into());\n        self\n    }\n\n    /// Set the label of the Bar.\n    pub fn label<F>(mut self, label: F) -> Self\n    where\n        F: Fn(&T, Point<Pixels>) -> Vec<Text> + 'static,\n    {\n        self.label = Some(Box::new(label));\n        self\n    }\n\n    fn path(&self, bounds: &Bounds<Pixels>) -> (Vec<PaintQuad>, PlotLabel) {\n        let origin = bounds.origin;\n        let mut graph = vec![];\n        let mut labels = vec![];\n\n        for v in &self.data {\n            let x_tick = (self.x)(v);\n            let y_tick = (self.y1)(v);\n            let y0 = (self.y0)(v);\n\n            if let (Some(x_tick), Some(y_tick)) = (x_tick, y_tick) {\n                let is_negative = y_tick > y0;\n                let (p1, p2) = if is_negative {\n                    (\n                        origin_point(px(x_tick), px(y0), origin),\n                        origin_point(px(x_tick + self.band_width), px(y_tick), origin),\n                    )\n                } else {\n                    (\n                        origin_point(px(x_tick), px(y_tick), origin),\n                        origin_point(px(x_tick + self.band_width), px(y0), origin),\n                    )\n                };\n\n                let color = (self.fill)(v);\n\n                graph.push(fill(Bounds::from_corners(p1, p2), color));\n\n                if let Some(label) = &self.label {\n                    labels.extend(label(\n                        v,\n                        point(\n                            px(x_tick + self.band_width / 2.),\n                            if is_negative {\n                                px(y_tick + TEXT_GAP)\n                            } else {\n                                px(y_tick - TEXT_HEIGHT)\n                            },\n                        ),\n                    ));\n                }\n            }\n        }\n\n        (graph, PlotLabel::new(labels))\n    }\n\n    /// Paint the Bar.\n    pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window, cx: &mut App) {\n        let (graph, labels) = self.path(bounds);\n        for quad in graph {\n            window.paint_quad(quad);\n        }\n        labels.paint(bounds, window, cx);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/shape/line.rs",
    "content": "// @reference: https://d3js.org/d3-shape/line\n\nuse gpui::{\n    Background, BorderStyle, Bounds, Hsla, PaintQuad, Path, PathBuilder, Pixels, Point, Window, px,\n    quad, size,\n};\n\nuse crate::plot::{StrokeStyle, origin_point};\n\n#[allow(clippy::type_complexity)]\npub struct Line<T> {\n    data: Vec<T>,\n    x: Box<dyn Fn(&T) -> Option<f32>>,\n    y: Box<dyn Fn(&T) -> Option<f32>>,\n    stroke: Background,\n    stroke_width: Pixels,\n    stroke_style: StrokeStyle,\n    dot: bool,\n    dot_size: Pixels,\n    dot_fill_color: Hsla,\n    dot_stroke_color: Option<Hsla>,\n}\n\nimpl<T> Default for Line<T> {\n    fn default() -> Self {\n        Self {\n            data: Vec::new(),\n            x: Box::new(|_| None),\n            y: Box::new(|_| None),\n            stroke: Default::default(),\n            stroke_width: px(1.),\n            stroke_style: Default::default(),\n            dot: false,\n            dot_size: px(4.),\n            dot_fill_color: gpui::transparent_black(),\n            dot_stroke_color: None,\n        }\n    }\n}\n\nimpl<T> Line<T> {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the data of the Line.\n    pub fn data<I>(mut self, data: I) -> Self\n    where\n        I: IntoIterator<Item = T>,\n    {\n        self.data = data.into_iter().collect();\n        self\n    }\n\n    /// Set the x of the Line.\n    pub fn x<F>(mut self, x: F) -> Self\n    where\n        F: Fn(&T) -> Option<f32> + 'static,\n    {\n        self.x = Box::new(x);\n        self\n    }\n\n    /// Set the y of the Line.\n    pub fn y<F>(mut self, y: F) -> Self\n    where\n        F: Fn(&T) -> Option<f32> + 'static,\n    {\n        self.y = Box::new(y);\n        self\n    }\n\n    /// Set the stroke color of the Line.\n    pub fn stroke(mut self, stroke: impl Into<Background>) -> Self {\n        self.stroke = stroke.into();\n        self\n    }\n\n    /// Set the stroke width of the Line.\n    pub fn stroke_width(mut self, stroke_width: impl Into<Pixels>) -> Self {\n        self.stroke_width = stroke_width.into();\n        self\n    }\n\n    /// Set the stroke style of the Line.\n    pub fn stroke_style(mut self, stroke_style: StrokeStyle) -> Self {\n        self.stroke_style = stroke_style;\n        self\n    }\n\n    /// Show dots on the Line.\n    pub fn dot(mut self) -> Self {\n        self.dot = true;\n        self\n    }\n\n    /// Set the size of the dots on the Line.\n    pub fn dot_size(mut self, dot_size: impl Into<Pixels>) -> Self {\n        self.dot_size = dot_size.into();\n        self\n    }\n\n    /// Set the fill color of the dots on the Line.\n    pub fn dot_fill_color(mut self, dot_fill_color: impl Into<Hsla>) -> Self {\n        self.dot_fill_color = dot_fill_color.into();\n        self\n    }\n\n    /// Set the stroke color of the dots on the Line.\n    pub fn dot_stroke_color(mut self, dot_stroke_color: impl Into<Hsla>) -> Self {\n        self.dot_stroke_color = Some(dot_stroke_color.into());\n        self\n    }\n\n    /// Paint the dots on the Line.\n    fn paint_dot(&self, dot: Point<Pixels>) -> PaintQuad {\n        quad(\n            gpui::bounds(dot, size(self.dot_size, self.dot_size)),\n            self.dot_size / 2.,\n            self.dot_fill_color,\n            px(1.),\n            self.dot_stroke_color.unwrap_or(self.dot_fill_color),\n            BorderStyle::default(),\n        )\n    }\n\n    fn path(&self, bounds: &Bounds<Pixels>) -> (Option<Path<Pixels>>, Vec<PaintQuad>) {\n        let origin = bounds.origin;\n        let mut builder = PathBuilder::stroke(self.stroke_width);\n        let mut dots = vec![];\n        let mut paint_dots = vec![];\n\n        for v in self.data.iter() {\n            let x_tick = (self.x)(v);\n            let y_tick = (self.y)(v);\n\n            if let (Some(x), Some(y)) = (x_tick, y_tick) {\n                let pos = origin_point(px(x), px(y), origin);\n\n                if self.dot {\n                    let dot_radius = self.dot_size.as_f32() / 2.;\n                    let dot_pos = origin_point(px(x - dot_radius), px(y - dot_radius), origin);\n                    paint_dots.push(self.paint_dot(dot_pos));\n                }\n\n                dots.push(pos);\n            }\n        }\n\n        if dots.is_empty() {\n            return (None, paint_dots);\n        }\n\n        if dots.len() == 1 {\n            builder.move_to(dots[0]);\n            return (builder.build().ok(), paint_dots);\n        }\n\n        match self.stroke_style {\n            StrokeStyle::Natural => {\n                builder.move_to(dots[0]);\n                let n = dots.len();\n                for i in 0..n - 1 {\n                    let p0 = if i == 0 { dots[0] } else { dots[i - 1] };\n                    let p1 = dots[i];\n                    let p2 = dots[i + 1];\n                    let p3 = if i + 2 < n { dots[i + 2] } else { dots[n - 1] };\n\n                    // Catmull-Rom to Bezier\n                    let c1 = Point::new(p1.x + (p2.x - p0.x) / 6.0, p1.y + (p2.y - p0.y) / 6.0);\n                    let c2 = Point::new(p2.x - (p3.x - p1.x) / 6.0, p2.y - (p3.y - p1.y) / 6.0);\n\n                    builder.cubic_bezier_to(p2, c1, c2);\n                }\n            }\n            StrokeStyle::Linear => {\n                builder.move_to(dots[0]);\n                for p in &dots[1..] {\n                    builder.line_to(*p);\n                }\n            }\n            StrokeStyle::StepAfter => {\n                builder.move_to(dots[0]);\n                for (i, p) in dots.windows(2).enumerate() {\n                    builder.line_to(Point::new(p[1].x, p[0].y));\n                    // Don't draw the vertical line for the last point\n                    if i < dots.len() - 2 {\n                        builder.line_to(p[1]);\n                    }\n                }\n            }\n        }\n\n        (builder.build().ok(), paint_dots)\n    }\n\n    /// Paint the Line.\n    pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window) {\n        let (path, dots) = self.path(bounds);\n        if let Some(path) = path {\n            window.paint_path(path, self.stroke);\n        }\n        for dot in dots {\n            window.paint_quad(dot);\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    use gpui::{Bounds, point, px};\n\n    #[test]\n    fn test_line_path() {\n        let data = vec![1., 2., 3.];\n        let line = Line::new()\n            .data(data.clone())\n            .x(|v| Some(*v))\n            .y(|v| Some(*v * 2.));\n\n        let bounds = Bounds::new(point(px(0.), px(0.)), size(px(100.), px(100.)));\n        let (path, dots) = line.path(&bounds);\n\n        assert!(path.is_some());\n        assert!(dots.is_empty());\n\n        let line_with_dots = Line::new()\n            .data(data)\n            .x(|v| Some(*v))\n            .y(|v| Some(*v * 2.))\n            .dot();\n\n        let (_, dots) = line_with_dots.path(&bounds);\n        assert_eq!(dots.len(), 3);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/shape/pie.rs",
    "content": "// @reference: https://d3js.org/d3-shape/pie\n\nuse std::f32::consts::TAU;\n\nuse super::arc::ArcData;\n\n#[allow(clippy::type_complexity)]\npub struct Pie<T> {\n    value: Box<dyn Fn(&T) -> Option<f32>>,\n    start_angle: f32,\n    end_angle: f32,\n    pad_angle: f32,\n}\n\nimpl<T> Default for Pie<T> {\n    fn default() -> Self {\n        Self {\n            value: Box::new(|_| None),\n            start_angle: 0.,\n            end_angle: TAU,\n            pad_angle: 0.,\n        }\n    }\n}\n\nimpl<T> Pie<T> {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the value of the Pie.\n    pub fn value<F>(mut self, value: F) -> Self\n    where\n        F: 'static + Fn(&T) -> Option<f32>,\n    {\n        self.value = Box::new(value);\n        self\n    }\n\n    /// Set the start angle of the Pie.\n    pub fn start_angle(mut self, start_angle: f32) -> Self {\n        self.start_angle = start_angle;\n        self\n    }\n\n    /// Set the end angle of the Pie.\n    pub fn end_angle(mut self, end_angle: f32) -> Self {\n        self.end_angle = end_angle;\n        self\n    }\n\n    /// Set the pad angle of the Pie.\n    pub fn pad_angle(mut self, pad_angle: f32) -> Self {\n        self.pad_angle = pad_angle;\n        self\n    }\n\n    /// Get the arcs of the Pie.\n    pub fn arcs<'a>(&self, data: &'a [T]) -> Vec<ArcData<'a, T>> {\n        let mut values = Vec::new();\n        let mut sum = 0.;\n\n        for (idx, v) in data.iter().enumerate() {\n            if let Some(value) = (self.value)(v) {\n                if value > 0. {\n                    sum += value;\n                    values.push((idx, v, value));\n                }\n            }\n        }\n\n        let mut arcs = Vec::with_capacity(values.len());\n        let mut k = self.start_angle;\n\n        for (index, v, value) in values {\n            let start_angle = k;\n            let angle_delta = if sum > 0. {\n                (value / sum) * (self.end_angle - self.start_angle)\n            } else {\n                0.\n            };\n            k += angle_delta;\n            let end_angle = k;\n\n            arcs.push(ArcData {\n                data: v,\n                index,\n                value,\n                start_angle,\n                end_angle,\n                pad_angle: self.pad_angle,\n            });\n        }\n\n        arcs\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_pie() {\n        let pie = Pie::new().value(|v| Some(*v));\n\n        let data = vec![1., 1., 1.];\n        let arcs = pie.arcs(&data);\n\n        assert_eq!(arcs.len(), 3);\n\n        assert_eq!(arcs[0].value, 1.);\n        assert_eq!(arcs[1].value, 1.);\n        assert_eq!(arcs[2].value, 1.);\n\n        assert_eq!(arcs[0].start_angle, 0.);\n        assert_eq!(arcs[0].end_angle, arcs[1].start_angle);\n        assert_eq!(arcs[1].end_angle, arcs[2].start_angle);\n        assert_eq!(arcs[2].end_angle, TAU);\n    }\n\n    #[test]\n    fn test_pie_zero_values() {\n        let pie = Pie::new().value(|v| Some(*v));\n        let data = vec![0., 1., 0., 2.];\n        let arcs = pie.arcs(&data);\n\n        assert_eq!(arcs.len(), 2);\n        assert_eq!(arcs[0].value, 1.);\n        assert_eq!(arcs[1].value, 2.);\n        assert_eq!(arcs[0].index, 1);\n        assert_eq!(arcs[1].index, 3);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/shape/stack.rs",
    "content": "// @reference: https://d3js.org/d3-shape/stack\n\n/// Represents a stacked series data point with lower and upper values\n#[derive(Clone, Debug)]\npub struct StackPoint<T> {\n    /// The lower value (baseline)\n    pub y0: f32,\n    /// The upper value (topline)\n    pub y1: f32,\n    /// Reference to the original data\n    pub data: T,\n}\n\n/// Represents a stacked series\n#[derive(Clone, Debug)]\npub struct StackSeries<T> {\n    /// The key for this series\n    pub key: String,\n    /// The index of this series\n    pub index: usize,\n    /// The points in this series\n    pub points: Vec<StackPoint<T>>,\n}\n\n#[allow(clippy::type_complexity)]\npub struct Stack<T> {\n    data: Vec<T>,\n    keys: Vec<String>,\n    value: Box<dyn Fn(&T, &str) -> Option<f32>>,\n}\n\nimpl<T: Clone> Default for Stack<T> {\n    fn default() -> Self {\n        Self {\n            data: Vec::new(),\n            keys: Vec::new(),\n            value: Box::new(|_, _| None),\n        }\n    }\n}\n\nimpl<T: Clone> Stack<T> {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the data to be stacked\n    pub fn data<I>(mut self, data: I) -> Self\n    where\n        I: IntoIterator<Item = T>,\n    {\n        self.data = data.into_iter().collect();\n        self\n    }\n\n    /// Set the keys (series) for stacking\n    pub fn keys<I, S>(mut self, keys: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.keys = keys.into_iter().map(|s| s.into()).collect();\n        self\n    }\n\n    /// Set the value accessor function\n    pub fn value<F>(mut self, value: F) -> Self\n    where\n        F: Fn(&T, &str) -> Option<f32> + 'static,\n    {\n        self.value = Box::new(value);\n        self\n    }\n\n    /// Compute the stacked series\n    pub fn series(&self) -> Vec<StackSeries<T>> {\n        if self.data.is_empty() || self.keys.is_empty() {\n            return Vec::new();\n        }\n\n        let n = self.data.len(); // number of data points\n        let m = self.keys.len(); // number of series\n\n        // Extract values into a 2D matrix: series x data points\n        let mut matrix: Vec<Vec<f32>> = Vec::with_capacity(m);\n        for key in &self.keys {\n            let mut series_values = Vec::with_capacity(n);\n            for datum in &self.data {\n                let value = (self.value)(datum, key).unwrap_or(0.0);\n                series_values.push(value);\n            }\n            matrix.push(series_values);\n        }\n\n        // Use the natural key order for stacking\n        let order: Vec<usize> = (0..m).collect();\n\n        // Initialize stacks with zeros\n        let mut stacks: Vec<Vec<(f32, f32)>> = vec![vec![(0.0, 0.0); n]; m];\n\n        // Compute the stacks based on order\n        for j in 0..n {\n            let mut y0 = 0.0;\n            for &i in &order {\n                let y1 = y0 + matrix[i][j];\n                stacks[i][j] = (y0, y1);\n                y0 = y1;\n            }\n        }\n\n        // Build the result series\n        let mut result = Vec::with_capacity(m);\n        for (i, key) in self.keys.iter().enumerate() {\n            let points = self\n                .data\n                .iter()\n                .enumerate()\n                .map(|(j, datum)| StackPoint {\n                    y0: stacks[i][j].0,\n                    y1: stacks[i][j].1,\n                    data: datum.clone(),\n                })\n                .collect();\n\n            result.push(StackSeries {\n                key: key.clone(),\n                index: i,\n                points,\n            });\n        }\n\n        result\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[derive(Clone, Debug)]\n    struct SalesData {\n        #[allow(dead_code)]\n        date: String,\n        apples: f32,\n        bananas: f32,\n        cherries: f32,\n    }\n\n    #[test]\n    fn test_basic_stack() {\n        let data = vec![\n            SalesData {\n                date: \"Jan\".to_string(),\n                apples: 10.0,\n                bananas: 20.0,\n                cherries: 30.0,\n            },\n            SalesData {\n                date: \"Feb\".to_string(),\n                apples: 15.0,\n                bananas: 25.0,\n                cherries: 35.0,\n            },\n        ];\n\n        let stack = Stack::new()\n            .data(data)\n            .keys(vec![\"apples\", \"bananas\", \"cherries\"])\n            .value(|d, key| match key {\n                \"apples\" => Some(d.apples),\n                \"bananas\" => Some(d.bananas),\n                \"cherries\" => Some(d.cherries),\n                _ => None,\n            });\n\n        let series = stack.series();\n\n        assert_eq!(series.len(), 3);\n        assert_eq!(series[0].key, \"apples\");\n        assert_eq!(series[0].points[0].y0, 0.0);\n        assert_eq!(series[0].points[0].y1, 10.0);\n\n        assert_eq!(series[1].key, \"bananas\");\n        assert_eq!(series[1].points[0].y0, 10.0);\n        assert_eq!(series[1].points[0].y1, 30.0);\n\n        assert_eq!(series[2].key, \"cherries\");\n        assert_eq!(series[2].points[0].y0, 30.0);\n        assert_eq!(series[2].points[0].y1, 60.0);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/plot/shape.rs",
    "content": "mod arc;\nmod area;\nmod bar;\nmod line;\nmod pie;\nmod stack;\n\npub use arc::{Arc, ArcData};\npub use area::Area;\npub use bar::Bar;\npub use line::Line;\npub use pie::Pie;\npub use stack::{Stack, StackPoint, StackSeries};\n"
  },
  {
    "path": "crates/ui/src/plot/tooltip.rs",
    "content": "use gpui::{\n    AnyElement, App, Div, Half as _, Hsla, IntoElement, ParentElement, Pixels, Point, RenderOnce,\n    StyleRefinement, Styled, Window, div, prelude::FluentBuilder, px,\n};\n\nuse crate::{ActiveTheme, v_flex};\n\n#[derive(Default)]\npub enum CrossLineAxis {\n    #[default]\n    Vertical,\n    Horizontal,\n    Both,\n}\n\nimpl CrossLineAxis {\n    /// Returns true if the cross line axis is vertical or both.\n    #[inline]\n    pub fn show_vertical(&self) -> bool {\n        matches!(self, CrossLineAxis::Vertical | CrossLineAxis::Both)\n    }\n\n    /// Returns true if the cross line axis is horizontal or both.\n    #[inline]\n    pub fn show_horizontal(&self) -> bool {\n        matches!(self, CrossLineAxis::Horizontal | CrossLineAxis::Both)\n    }\n}\n\n#[derive(IntoElement)]\npub struct CrossLine {\n    point: Point<Pixels>,\n    height: Option<f32>,\n    direction: CrossLineAxis,\n}\n\nimpl CrossLine {\n    pub fn new(point: Point<Pixels>) -> Self {\n        Self {\n            point,\n            height: None,\n            direction: Default::default(),\n        }\n    }\n\n    /// Set the cross line axis to horizontal.\n    pub fn horizontal(mut self) -> Self {\n        self.direction = CrossLineAxis::Horizontal;\n        self\n    }\n\n    /// Set the cross line axis to both.\n    pub fn both(mut self) -> Self {\n        self.direction = CrossLineAxis::Both;\n        self\n    }\n\n    /// Set the height of the cross line.\n    pub fn height(mut self, height: f32) -> Self {\n        self.height = Some(height);\n        self\n    }\n}\n\nimpl From<Point<Pixels>> for CrossLine {\n    fn from(value: Point<Pixels>) -> Self {\n        Self::new(value)\n    }\n}\n\nimpl RenderOnce for CrossLine {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        div()\n            .size_full()\n            .absolute()\n            .top_0()\n            .left_0()\n            .when(self.direction.show_vertical(), |this| {\n                this.child(\n                    div()\n                        .absolute()\n                        .w(px(1.))\n                        .bg(cx.theme().border)\n                        .top_0()\n                        .left(self.point.x)\n                        .map(|this| {\n                            if let Some(height) = self.height {\n                                this.h(px(height))\n                            } else {\n                                this.h_full()\n                            }\n                        }),\n                )\n            })\n            .when(self.direction.show_horizontal(), |this| {\n                this.child(\n                    div()\n                        .absolute()\n                        .w_full()\n                        .h(px(1.))\n                        .bg(cx.theme().border)\n                        .left_0()\n                        .top(self.point.y),\n                )\n            })\n    }\n}\n\n#[derive(IntoElement)]\npub struct Dot {\n    point: Point<Pixels>,\n    size: Pixels,\n    stroke: Hsla,\n    fill: Hsla,\n}\n\nimpl Dot {\n    pub fn new(point: Point<Pixels>) -> Self {\n        Self {\n            point,\n            size: px(6.),\n            stroke: gpui::transparent_black(),\n            fill: gpui::transparent_black(),\n        }\n    }\n\n    /// Set the size of the dot.\n    pub fn size(mut self, size: impl Into<Pixels>) -> Self {\n        self.size = size.into();\n        self\n    }\n\n    /// Set the stroke of the dot.\n    pub fn stroke(mut self, stroke: Hsla) -> Self {\n        self.stroke = stroke;\n        self\n    }\n\n    /// Set the fill of the dot.\n    pub fn fill(mut self, fill: Hsla) -> Self {\n        self.fill = fill;\n        self\n    }\n}\n\nimpl RenderOnce for Dot {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        let border_width = px(1.);\n        let offset = self.size / 2. - border_width / 2.;\n\n        div()\n            .absolute()\n            .w(self.size)\n            .h(self.size)\n            .rounded_full()\n            .border(border_width)\n            .border_color(self.stroke)\n            .bg(self.fill)\n            .left(self.point.x - offset)\n            .top(self.point.y - offset)\n    }\n}\n\n#[derive(Clone, Copy, Default, PartialEq, Eq)]\npub enum TooltipPosition {\n    #[default]\n    Left,\n    Right,\n}\n\n#[derive(Clone)]\npub struct TooltipState {\n    pub index: usize,\n    pub cross_line: Point<Pixels>,\n    pub dots: Vec<Point<Pixels>>,\n    pub position: TooltipPosition,\n}\n\nimpl TooltipState {\n    pub fn new(\n        index: usize,\n        cross_line: Point<Pixels>,\n        dots: Vec<Point<Pixels>>,\n        position: TooltipPosition,\n    ) -> Self {\n        Self {\n            index,\n            cross_line,\n            dots,\n            position,\n        }\n    }\n}\n\n#[derive(IntoElement)]\npub struct Tooltip {\n    base: Div,\n    position: Option<TooltipPosition>,\n    gap: Pixels,\n    cross_line: Option<CrossLine>,\n    dots: Option<Vec<Dot>>,\n    appearance: bool,\n}\n\nimpl Tooltip {\n    #[allow(clippy::new_without_default)]\n    pub fn new() -> Self {\n        Self {\n            base: v_flex().top_0(),\n            position: Default::default(),\n            gap: px(0.),\n            cross_line: None,\n            dots: None,\n            appearance: true,\n        }\n    }\n\n    /// Set the position of the tooltip.\n    pub fn position(mut self, position: TooltipPosition) -> Self {\n        self.position = Some(position);\n        self\n    }\n\n    /// Set the gap of the tooltip.\n    pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {\n        self.gap = gap.into();\n        self\n    }\n\n    /// Set the cross line of the tooltip.\n    pub fn cross_line(mut self, cross_line: CrossLine) -> Self {\n        self.cross_line = Some(cross_line);\n        self\n    }\n\n    /// Set the dots of the tooltip.\n    pub fn dots(mut self, dots: impl IntoIterator<Item = Dot>) -> Self {\n        self.dots = Some(dots.into_iter().collect());\n        self\n    }\n\n    /// Set the appearance of the tooltip.\n    pub fn appearance(mut self, appearance: bool) -> Self {\n        self.appearance = appearance;\n        self\n    }\n}\n\nimpl Styled for Tooltip {\n    fn style(&mut self) -> &mut StyleRefinement {\n        self.base.style()\n    }\n}\n\nimpl ParentElement for Tooltip {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.base.extend(elements);\n    }\n}\n\nimpl RenderOnce for Tooltip {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        div()\n            .size_full()\n            .absolute()\n            .top_0()\n            .left_0()\n            .when_some(self.cross_line, |this, cross_line| this.child(cross_line))\n            .when_some(self.dots, |this, dots| this.children(dots))\n            .child(self.base.map(|this| {\n                if self.appearance {\n                    this.absolute()\n                        .min_w(px(168.))\n                        .p_2()\n                        .border_1()\n                        .border_color(cx.theme().border)\n                        .rounded(cx.theme().radius.half())\n                        .bg(cx.theme().background.opacity(0.9))\n                        .when_some(self.position, |this, position| {\n                            if position == TooltipPosition::Left {\n                                this.left(self.gap)\n                            } else {\n                                this.right(self.gap)\n                            }\n                        })\n                } else {\n                    this.size_full().relative()\n                }\n            }))\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/popover.rs",
    "content": "use gpui::{\n    AnyElement, App, Bounds, Context, Deferred, DismissEvent, Div, ElementId, EventEmitter,\n    FocusHandle, Focusable, Half, InteractiveElement as _, IntoElement, KeyBinding, MouseButton,\n    ParentElement, Pixels, Point, Render, RenderOnce, Stateful, StyleRefinement, Styled,\n    Subscription, Window, deferred, div, prelude::FluentBuilder as _, px,\n};\nuse std::rc::Rc;\n\nuse crate::{\n    Anchor, ElementExt, Selectable, StyledExt as _, actions::Cancel, anchored,\n    global_state::GlobalState, v_flex,\n};\n\nconst CONTEXT: &str = \"Popover\";\npub(crate) fn init(cx: &mut App) {\n    cx.bind_keys([KeyBinding::new(\"escape\", Cancel, Some(CONTEXT))])\n}\n\n/// A popover element that can be triggered by a button or any other element.\n#[derive(IntoElement)]\npub struct Popover {\n    id: ElementId,\n    style: StyleRefinement,\n    anchor: Anchor,\n    default_open: bool,\n    open: Option<bool>,\n    tracked_focus_handle: Option<FocusHandle>,\n    trigger: Option<Box<dyn FnOnce(bool, &Window, &App) -> AnyElement + 'static>>,\n    content: Option<\n        Rc<\n            dyn Fn(&mut PopoverState, &mut Window, &mut Context<PopoverState>) -> AnyElement\n                + 'static,\n        >,\n    >,\n    children: Vec<AnyElement>,\n    /// Style for trigger element.\n    /// This is used for hotfix the trigger element style to support w_full.\n    trigger_style: Option<StyleRefinement>,\n    mouse_button: MouseButton,\n    appearance: bool,\n    overlay_closable: bool,\n    on_open_change: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,\n}\n\nimpl Popover {\n    /// Create a new Popover with `view` mode.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default(),\n            anchor: Anchor::TopLeft,\n            trigger: None,\n            trigger_style: None,\n            content: None,\n            tracked_focus_handle: None,\n            children: vec![],\n            mouse_button: MouseButton::Left,\n            appearance: true,\n            overlay_closable: true,\n            default_open: false,\n            open: None,\n            on_open_change: None,\n        }\n    }\n\n    /// Set the anchor corner of the popover, default is `Corner::TopLeft`.\n    ///\n    /// This method is kept for backward compatibility with `Corner` type.\n    /// Internally, it converts `Corner` to `Anchor`.\n    pub fn anchor(mut self, anchor: impl Into<Anchor>) -> Self {\n        self.anchor = anchor.into();\n        self\n    }\n\n    /// Set the mouse button to trigger the popover, default is `MouseButton::Left`.\n    pub fn mouse_button(mut self, mouse_button: MouseButton) -> Self {\n        self.mouse_button = mouse_button;\n        self\n    }\n\n    /// Set the trigger element of the popover.\n    pub fn trigger<T>(mut self, trigger: T) -> Self\n    where\n        T: Selectable + IntoElement + 'static,\n    {\n        self.trigger = Some(Box::new(|is_open, _, _| {\n            let selected = trigger.is_selected();\n            trigger.selected(selected || is_open).into_any_element()\n        }));\n        self\n    }\n\n    /// Set the default open state of the popover, default is `false`.\n    ///\n    /// This is only used to initialize the open state of the popover.\n    ///\n    /// And please note that if you use the `open` method, this value will be ignored.\n    pub fn default_open(mut self, open: bool) -> Self {\n        self.default_open = open;\n        self\n    }\n\n    /// Force set the open state of the popover.\n    ///\n    /// If this is set, the popover will be controlled by this value.\n    ///\n    /// NOTE: You must be used in conjunction with `on_open_change` to handle state changes.\n    pub fn open(mut self, open: bool) -> Self {\n        self.open = Some(open);\n        self\n    }\n\n    /// Add a callback to be called when the open state changes.\n    ///\n    /// The first `&bool` parameter is the **new open state**.\n    ///\n    /// This is useful when using the `open` method to control the popover state.\n    pub fn on_open_change<F>(mut self, callback: F) -> Self\n    where\n        F: Fn(&bool, &mut Window, &mut App) + 'static,\n    {\n        self.on_open_change = Some(Rc::new(callback));\n        self\n    }\n\n    /// Set the style for the trigger element.\n    pub fn trigger_style(mut self, style: StyleRefinement) -> Self {\n        self.trigger_style = Some(style);\n        self\n    }\n\n    /// Set whether clicking outside the popover will dismiss it, default is `true`.\n    pub fn overlay_closable(mut self, closable: bool) -> Self {\n        self.overlay_closable = closable;\n        self\n    }\n\n    /// Set the content builder for content of the Popover.\n    ///\n    /// This callback will called every time on render the popover.\n    /// So, you should avoid creating new elements or entities in the content closure.\n    pub fn content<F, E>(mut self, content: F) -> Self\n    where\n        E: IntoElement,\n        F: Fn(&mut PopoverState, &mut Window, &mut Context<PopoverState>) -> E + 'static,\n    {\n        self.content = Some(Rc::new(move |state, window, cx| {\n            content(state, window, cx).into_any_element()\n        }));\n        self\n    }\n\n    /// Set whether the popover no style, default is `false`.\n    ///\n    /// If no style:\n    ///\n    /// - The popover will not have a bg, border, shadow, or padding.\n    /// - The click out of the popover will not dismiss it.\n    pub fn appearance(mut self, appearance: bool) -> Self {\n        self.appearance = appearance;\n        self\n    }\n\n    /// Bind the focus handle to receive focus when the popover is opened.\n    /// If you not set this, a new focus handle will be created for the popover to\n    ///\n    /// If popover is opened, the focus will be moved to the focus handle.\n    pub fn track_focus(mut self, handle: &FocusHandle) -> Self {\n        self.tracked_focus_handle = Some(handle.clone());\n        self\n    }\n\n    fn resolved_corner(anchor: Anchor, trigger_bounds: Bounds<Pixels>) -> Point<Pixels> {\n        let offset = if anchor.is_center() {\n            gpui::point(trigger_bounds.size.width.half(), px(0.))\n        } else {\n            Point::default()\n        };\n\n        trigger_bounds.corner(anchor.swap_vertical().into())\n            + offset\n            + Point {\n                x: px(0.),\n                y: -trigger_bounds.size.height,\n            }\n    }\n}\n\nimpl ParentElement for Popover {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Styled for Popover {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\npub struct PopoverState {\n    focus_handle: FocusHandle,\n    pub(crate) tracked_focus_handle: Option<FocusHandle>,\n    trigger_bounds: Bounds<Pixels>,\n    open: bool,\n    on_open_change: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,\n\n    _dismiss_subscription: Option<Subscription>,\n}\n\nimpl PopoverState {\n    pub fn new(default_open: bool, cx: &mut App) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            tracked_focus_handle: None,\n            trigger_bounds: Bounds::default(),\n            open: default_open,\n            on_open_change: None,\n            _dismiss_subscription: None,\n        }\n    }\n\n    /// Check if the popover is open.\n    pub fn is_open(&self) -> bool {\n        self.open\n    }\n\n    /// Dismiss the popover if it is open.\n    pub fn dismiss(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        if self.open {\n            self.toggle_open(window, cx);\n        }\n    }\n\n    /// Open the popover if it is closed.\n    pub fn show(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        if !self.open {\n            self.toggle_open(window, cx);\n        }\n    }\n\n    fn set_open(&mut self, open: bool, cx: &mut Context<Self>) {\n        self.open = open;\n        if self.open {\n            GlobalState::global_mut(cx).register_deferred_popover(&self.focus_handle);\n        } else {\n            GlobalState::global_mut(cx).unregister_deferred_popover(&self.focus_handle);\n        }\n    }\n\n    fn toggle_open(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        self.set_open(!self.open, cx);\n        if self.open {\n            let state = cx.entity();\n            let focus_handle = if let Some(tracked_focus_handle) = self.tracked_focus_handle.clone()\n            {\n                tracked_focus_handle\n            } else {\n                self.focus_handle.clone()\n            };\n            focus_handle.focus(window, cx);\n\n            self._dismiss_subscription =\n                Some(\n                    window.subscribe(&cx.entity(), cx, move |_, _: &DismissEvent, window, cx| {\n                        state.update(cx, |state, cx| {\n                            state.dismiss(window, cx);\n                        });\n                        window.refresh();\n                    }),\n                );\n        } else {\n            self._dismiss_subscription = None;\n        }\n\n        if let Some(callback) = self.on_open_change.as_ref() {\n            callback(&self.open, window, cx);\n        }\n        cx.notify();\n    }\n\n    fn on_action_cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {\n        self.dismiss(window, cx);\n    }\n}\n\nimpl Focusable for PopoverState {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for PopoverState {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        div()\n    }\n}\n\nimpl EventEmitter<DismissEvent> for PopoverState {}\n\nimpl Popover {\n    pub(crate) fn render_popover<E>(\n        anchor: Anchor,\n        trigger_bounds: Bounds<Pixels>,\n        content: E,\n        _: &mut Window,\n        _: &mut App,\n    ) -> Deferred\n    where\n        E: IntoElement + 'static,\n    {\n        deferred(\n            anchored()\n                .snap_to_window_with_margin(px(8.))\n                .anchor(anchor)\n                .position(Self::resolved_corner(anchor, trigger_bounds))\n                .child(div().relative().child(content)),\n        )\n        .with_priority(1)\n    }\n\n    pub(crate) fn render_popover_content(\n        anchor: Anchor,\n        appearance: bool,\n        _: &mut Window,\n        cx: &mut App,\n    ) -> Stateful<Div> {\n        v_flex()\n            .id(\"content\")\n            .occlude()\n            .tab_group()\n            .when(appearance, |this| this.popover_style(cx).p_3())\n            .map(|this| match anchor {\n                Anchor::TopLeft | Anchor::TopCenter | Anchor::TopRight => this.top_1(),\n                Anchor::BottomLeft | Anchor::BottomCenter | Anchor::BottomRight => this.bottom_1(),\n            })\n    }\n}\n\nimpl RenderOnce for Popover {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let force_open = self.open;\n        let default_open = self.default_open;\n        let tracked_focus_handle = self.tracked_focus_handle.clone();\n        let state = window.use_keyed_state(self.id.clone(), cx, |_, cx| {\n            PopoverState::new(default_open, cx)\n        });\n\n        state.update(cx, |state, cx| {\n            if let Some(tracked_focus_handle) = tracked_focus_handle {\n                state.tracked_focus_handle = Some(tracked_focus_handle);\n            }\n            state.on_open_change = self.on_open_change.clone();\n            if let Some(force_open) = force_open {\n                state.set_open(force_open, cx);\n            }\n        });\n\n        let open = state.read(cx).open;\n        let focus_handle = state.read(cx).focus_handle.clone();\n        let trigger_bounds = state.read(cx).trigger_bounds;\n\n        let Some(trigger) = self.trigger else {\n            return div().id(\"empty\");\n        };\n\n        let parent_view_id = window.current_view();\n\n        let el = div()\n            .id(self.id)\n            .child((trigger)(open, window, cx))\n            .on_mouse_down(self.mouse_button, {\n                let state = state.clone();\n                move |_, window, cx| {\n                    cx.stop_propagation();\n                    state.update(cx, |state, cx| {\n                        // We force set open to false to toggle it correctly.\n                        // Because if the mouse down out will toggle open first.\n                        state.set_open(open, cx);\n                        state.toggle_open(window, cx);\n                    });\n                    cx.notify(parent_view_id);\n                }\n            })\n            .on_prepaint({\n                let state = state.clone();\n                move |bounds, _, cx| {\n                    state.update(cx, |state, _| {\n                        state.trigger_bounds = bounds;\n                    })\n                }\n            });\n\n        if !open {\n            return el;\n        }\n\n        let popover_content =\n            Self::render_popover_content(self.anchor, self.appearance, window, cx)\n                .track_focus(&focus_handle)\n                .key_context(CONTEXT)\n                .on_action(window.listener_for(&state, PopoverState::on_action_cancel))\n                .when_some(self.content, |this, content| {\n                    this.child(state.update(cx, |state, cx| (content)(state, window, cx)))\n                })\n                .children(self.children)\n                .when(self.overlay_closable, |this| {\n                    this.on_mouse_down_out({\n                        let state = state.clone();\n                        move |_, window, cx| {\n                            state.update(cx, |state, cx| {\n                                state.dismiss(window, cx);\n                            });\n                            cx.notify(parent_view_id);\n                        }\n                    })\n                })\n                .refine_style(&self.style);\n\n        el.child(Self::render_popover(\n            self.anchor,\n            trigger_bounds,\n            popover_content,\n            window,\n            cx,\n        ))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use gpui::MouseButton;\n\n    #[test]\n    fn test_popover_builder_chaining() {\n        let popover = Popover::new(\"test\")\n            .anchor(Anchor::BottomCenter)\n            .mouse_button(MouseButton::Right)\n            .default_open(true)\n            .appearance(false)\n            .overlay_closable(false);\n\n        assert_eq!(popover.anchor, Anchor::BottomCenter);\n        assert_eq!(popover.mouse_button, MouseButton::Right);\n        assert!(popover.default_open);\n        assert!(!popover.appearance);\n        assert!(!popover.overlay_closable);\n    }\n\n    #[test]\n    fn test_resolved_corner_top_positions() {\n        use gpui::px;\n\n        let bounds = Bounds {\n            origin: Point {\n                x: px(100.),\n                y: px(100.),\n            },\n            size: gpui::Size {\n                width: px(200.),\n                height: px(50.),\n            },\n        };\n\n        let pos = Popover::resolved_corner(Anchor::TopLeft, bounds);\n        assert_eq!(pos.x, px(100.));\n        assert_eq!(pos.y, px(100.));\n\n        let pos = Popover::resolved_corner(Anchor::TopCenter, bounds);\n        assert_eq!(pos.x, px(200.));\n        assert_eq!(pos.y, px(100.));\n\n        let pos = Popover::resolved_corner(Anchor::TopRight, bounds);\n        assert_eq!(pos.x, px(300.));\n        assert_eq!(pos.y, px(100.));\n\n        let pos = Popover::resolved_corner(Anchor::BottomLeft, bounds);\n        assert_eq!(pos.x, px(100.));\n        assert_eq!(pos.y, px(50.));\n\n        let pos = Popover::resolved_corner(Anchor::BottomCenter, bounds);\n        assert_eq!(pos.x, px(200.));\n        assert_eq!(pos.y, px(50.));\n\n        let pos = Popover::resolved_corner(Anchor::BottomRight, bounds);\n        assert_eq!(pos.x, px(300.));\n        assert_eq!(pos.y, px(50.));\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/progress/mod.rs",
    "content": "mod progress;\nmod progress_circle;\n\npub use progress::Progress;\npub use progress_circle::ProgressCircle;\n\n/// Shared state for progress components.\npub(crate) struct ProgressState {\n    pub(crate) value: f32,\n}\n"
  },
  {
    "path": "crates/ui/src/progress/progress.rs",
    "content": "use crate::{ActiveTheme, Sizable, Size, StyledExt};\nuse gpui::{\n    Animation, AnimationExt as _, App, ElementId, Hsla, InteractiveElement as _, IntoElement,\n    ParentElement, RenderOnce, StyleRefinement, Styled, Window, div, prelude::FluentBuilder, px,\n    relative,\n};\nuse instant::Duration;\n\nuse super::ProgressState;\n\n/// A linear horizontal progress bar element.\n#[derive(IntoElement)]\npub struct Progress {\n    id: ElementId,\n    style: StyleRefinement,\n    color: Option<Hsla>,\n    value: f32,\n    size: Size,\n}\n\nimpl Progress {\n    /// Create a new Progress bar.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            value: Default::default(),\n            color: None,\n            style: StyleRefinement::default(),\n            size: Size::default(),\n        }\n    }\n\n    /// Set the color of the progress bar.\n    pub fn color(mut self, color: impl Into<Hsla>) -> Self {\n        self.color = Some(color.into());\n        self\n    }\n\n    /// Set the percentage value of the progress bar.\n    ///\n    /// The value should be between 0.0 and 100.0.\n    pub fn value(mut self, value: f32) -> Self {\n        self.value = value.clamp(0., 100.);\n        self\n    }\n}\n\nimpl Styled for Progress {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Sizable for Progress {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl RenderOnce for Progress {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let color = self.color.unwrap_or(cx.theme().progress_bar);\n        let value = self.value;\n\n        let radius = self.style.corner_radii.clone();\n        let mut inner_style = StyleRefinement::default();\n        inner_style.corner_radii = radius;\n\n        let (height, radius) = match self.size {\n            Size::XSmall => (px(4.), px(2.)),\n            Size::Small => (px(6.), px(3.)),\n            Size::Medium => (px(8.), px(4.)),\n            Size::Large => (px(10.), px(5.)),\n            Size::Size(s) => (s, s / 2.),\n        };\n\n        let state = window.use_keyed_state(self.id.clone(), cx, |_, _| ProgressState { value });\n        let prev_value = state.read(cx).value;\n\n        div()\n            .id(self.id)\n            .w_full()\n            .relative()\n            .rounded_full()\n            .h(height)\n            .rounded(radius)\n            .refine_style(&self.style)\n            .bg(color.opacity(0.2))\n            .child(\n                div()\n                    .absolute()\n                    .top_0()\n                    .left_0()\n                    .h_full()\n                    .bg(color)\n                    .rounded(radius)\n                    .refine_style(&inner_style)\n                    .map(|this| match value {\n                        v if v >= 100. => this,\n                        _ => this.rounded_r_none(),\n                    })\n                    .map(|this| {\n                        if prev_value != value {\n                            // Animate from prev_value to value\n                            let duration = Duration::from_secs_f64(0.15);\n                            cx.spawn({\n                                let state = state.clone();\n                                async move |cx| {\n                                    cx.background_executor().timer(duration).await;\n                                    _ = state.update(cx, |this, _| this.value = value);\n                                }\n                            })\n                            .detach();\n\n                            this.with_animation(\n                                \"progress-animation\",\n                                Animation::new(duration),\n                                move |this, delta| {\n                                    let current_value = prev_value + (value - prev_value) * delta;\n                                    let relative_w = relative(match current_value {\n                                        v if v < 0. => 0.,\n                                        v if v > 100. => 1.,\n                                        v => v / 100.,\n                                    });\n                                    this.w(relative_w)\n                                },\n                            )\n                            .into_any_element()\n                        } else {\n                            let relative_w = relative(match value {\n                                v if v < 0. => 0.,\n                                v if v > 100. => 1.,\n                                v => v / 100.,\n                            });\n                            this.w(relative_w).into_any_element()\n                        }\n                    }),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/progress/progress_circle.rs",
    "content": "use crate::{ActiveTheme, Sizable, Size, StyledExt};\nuse gpui::prelude::FluentBuilder as _;\nuse gpui::{\n    Animation, AnimationExt as _, AnyElement, App, ElementId, Hsla, InteractiveElement as _,\n    IntoElement, ParentElement, Pixels, RenderOnce, StyleRefinement, Styled, Window, canvas, px,\n    relative,\n};\nuse gpui::{Bounds, div};\nuse std::f32::consts::TAU;\nuse instant::Duration;\n\nuse super::ProgressState;\nuse crate::plot::shape::{Arc, ArcData};\n\n/// A circular progress indicator element.\n#[derive(IntoElement)]\npub struct ProgressCircle {\n    id: ElementId,\n    style: StyleRefinement,\n    color: Option<Hsla>,\n    value: f32,\n    size: Size,\n    children: Vec<AnyElement>,\n}\n\nimpl ProgressCircle {\n    /// Create a new circular progress indicator.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            value: Default::default(),\n            color: None,\n            style: StyleRefinement::default(),\n            size: Size::default(),\n            children: Vec::new(),\n        }\n    }\n\n    /// Set the color of the progress circle.\n    pub fn color(mut self, color: impl Into<Hsla>) -> Self {\n        self.color = Some(color.into());\n        self\n    }\n\n    /// Set the percentage value of the progress circle.\n    ///\n    /// The value should be between 0.0 and 100.0.\n    pub fn value(mut self, value: f32) -> Self {\n        self.value = value.clamp(0., 100.);\n        self\n    }\n\n    fn render_circle(current_value: f32, color: Hsla) -> impl IntoElement {\n        struct PrepaintState {\n            current_value: f32,\n            actual_inner_radius: f32,\n            actual_outer_radius: f32,\n            bounds: Bounds<Pixels>,\n        }\n\n        canvas(\n            {\n                let display_value = current_value;\n                move |bounds: Bounds<Pixels>, _window: &mut Window, _cx: &mut App| {\n                    // Use 15% of width as stroke width, but max 5px\n                    let stroke_width = (bounds.size.width * 0.15).min(px(5.));\n\n                    // Calculate actual size from bounds\n                    let actual_size = bounds.size.width.min(bounds.size.height);\n                    let actual_radius = (actual_size.as_f32() - stroke_width.as_f32()) / 2.;\n                    let actual_inner_radius = actual_radius - stroke_width.as_f32() / 2.;\n                    let actual_outer_radius = actual_radius + stroke_width.as_f32() / 2.;\n\n                    PrepaintState {\n                        current_value: display_value,\n                        actual_inner_radius,\n                        actual_outer_radius,\n                        bounds,\n                    }\n                }\n            },\n            move |_bounds, prepaint, window: &mut Window, _cx: &mut App| {\n                // Draw background circle\n                let bg_arc_data = ArcData {\n                    data: &(),\n                    index: 0,\n                    value: 100.,\n                    start_angle: 0.,\n                    end_angle: TAU,\n                    pad_angle: 0.,\n                };\n\n                let bg_arc = Arc::new()\n                    .inner_radius(prepaint.actual_inner_radius)\n                    .outer_radius(prepaint.actual_outer_radius);\n\n                bg_arc.paint(\n                    &bg_arc_data,\n                    color.opacity(0.2),\n                    None,\n                    None,\n                    &prepaint.bounds,\n                    window,\n                );\n\n                // Draw progress arc\n                if prepaint.current_value > 0. {\n                    let progress_angle = (prepaint.current_value / 100.) * TAU;\n                    let progress_arc_data = ArcData {\n                        data: &(),\n                        index: 1,\n                        value: prepaint.current_value,\n                        start_angle: 0.,\n                        end_angle: progress_angle,\n                        pad_angle: 0.,\n                    };\n\n                    let progress_arc = Arc::new()\n                        .inner_radius(prepaint.actual_inner_radius)\n                        .outer_radius(prepaint.actual_outer_radius);\n\n                    progress_arc.paint(\n                        &progress_arc_data,\n                        color,\n                        None,\n                        None,\n                        &prepaint.bounds,\n                        window,\n                    );\n                }\n            },\n        )\n        .absolute()\n        .size_full()\n    }\n}\n\nimpl Styled for ProgressCircle {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Sizable for ProgressCircle {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl ParentElement for ProgressCircle {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl RenderOnce for ProgressCircle {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let value = self.value;\n        let state = window.use_keyed_state(self.id.clone(), cx, |_, _| ProgressState { value });\n        let prev_value = state.read(cx).value;\n\n        let color = self.color.unwrap_or(cx.theme().progress_bar);\n        let has_changed = prev_value != value;\n\n        div()\n            .id(self.id.clone())\n            .flex()\n            .items_center()\n            .justify_center()\n            .line_height(relative(1.))\n            .map(|this| match self.size {\n                Size::XSmall => this.size_2(),\n                Size::Small => this.size_3(),\n                Size::Medium => this.size_4(),\n                Size::Large => this.size_5(),\n                Size::Size(s) => this.size(s * 0.75),\n            })\n            .refine_style(&self.style)\n            .children(self.children)\n            .map(|this| {\n                if has_changed {\n                    this.with_animation(\n                        format!(\"progress-circle-{}\", prev_value),\n                        Animation::new(Duration::from_secs_f64(0.15)),\n                        move |this, delta| {\n                            let animated_value = prev_value + (value - prev_value) * delta;\n                            this.child(Self::render_circle(animated_value, color))\n                        },\n                    )\n                    .into_any_element()\n                } else {\n                    this.child(Self::render_circle(value, color))\n                        .into_any_element()\n                }\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/radio.rs",
    "content": "use std::rc::Rc;\n\nuse crate::{\n    ActiveTheme, AxisExt, FocusableExt as _, Sizable, Size, StyledExt,\n    checkbox::checkbox_check_icon, h_flex, text::Text, v_flex,\n};\nuse gpui::{\n    AnyElement, App, Axis, Div, ElementId, InteractiveElement, IntoElement, ParentElement,\n    RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, div,\n    prelude::FluentBuilder, px, relative, rems,\n};\n\n/// A Radio element.\n///\n/// This is not included the Radio group implementation, you can manage the group by yourself.\n#[derive(IntoElement)]\npub struct Radio {\n    base: Div,\n    style: StyleRefinement,\n    id: ElementId,\n    label: Option<Text>,\n    children: Vec<AnyElement>,\n    checked: bool,\n    disabled: bool,\n    tab_stop: bool,\n    tab_index: isize,\n    size: Size,\n    on_click: Option<Rc<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,\n}\n\nimpl Radio {\n    /// Create a new Radio element with the given id.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            base: div(),\n            style: StyleRefinement::default(),\n            label: None,\n            children: Vec::new(),\n            checked: false,\n            disabled: false,\n            tab_index: 0,\n            tab_stop: true,\n            size: Size::default(),\n            on_click: None,\n        }\n    }\n\n    /// Set the label of the Radio element.\n    pub fn label(mut self, label: impl Into<Text>) -> Self {\n        self.label = Some(label.into());\n        self\n    }\n\n    /// Set the checked state of the Radio element, default is `false`.\n    pub fn checked(mut self, checked: bool) -> Self {\n        self.checked = checked;\n        self\n    }\n\n    /// Set the disabled state of the Radio element, default is `false`.\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    /// Set the tab index for the Radio element, default is `0`.\n    pub fn tab_index(mut self, tab_index: isize) -> Self {\n        self.tab_index = tab_index;\n        self\n    }\n\n    /// Set the tab stop for the Radio element, default is `true`.\n    pub fn tab_stop(mut self, tab_stop: bool) -> Self {\n        self.tab_stop = tab_stop;\n        self\n    }\n\n    /// Add on_click handler when the Radio is clicked.\n    ///\n    /// The `&bool` parameter is the **new checked state**.\n    pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {\n        self.on_click = Some(Rc::new(handler));\n        self\n    }\n\n    fn handle_click(\n        on_click: &Option<Rc<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,\n        checked: bool,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        let new_checked = !checked;\n        if let Some(f) = on_click {\n            (f)(&new_checked, window, cx);\n        }\n    }\n}\n\nimpl Sizable for Radio {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Styled for Radio {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl InteractiveElement for Radio {\n    fn interactivity(&mut self) -> &mut gpui::Interactivity {\n        self.base.interactivity()\n    }\n}\n\nimpl StatefulInteractiveElement for Radio {}\n\nimpl ParentElement for Radio {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl RenderOnce for Radio {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let checked = self.checked;\n        let focus_handle = window\n            .use_keyed_state(self.id.clone(), cx, |_, cx| cx.focus_handle())\n            .read(cx)\n            .clone();\n        let is_focused = focus_handle.is_focused(window);\n        let disabled = self.disabled;\n\n        let (border_color, bg) = if checked {\n            (cx.theme().primary, cx.theme().primary)\n        } else {\n            (cx.theme().input, cx.theme().input.opacity(0.5))\n        };\n        let (border_color, bg) = if disabled {\n            (border_color.opacity(0.5), bg.opacity(0.5))\n        } else {\n            (border_color, bg)\n        };\n\n        // wrap a flex to patch for let Radio display inline\n        div().child(\n            self.base\n                .id(self.id.clone())\n                .when(!self.disabled, |this| {\n                    this.track_focus(\n                        &focus_handle\n                            .tab_stop(self.tab_stop)\n                            .tab_index(self.tab_index),\n                    )\n                })\n                .h_flex()\n                .gap_x_2()\n                .text_color(cx.theme().foreground)\n                .items_start()\n                .line_height(relative(1.))\n                .rounded(cx.theme().radius * 0.5)\n                .focus_ring(is_focused, px(2.), window, cx)\n                .map(|this| match self.size {\n                    Size::XSmall => this.text_xs(),\n                    Size::Small => this.text_sm(),\n                    Size::Medium => this.text_base(),\n                    Size::Large => this.text_lg(),\n                    _ => this,\n                })\n                .refine_style(&self.style)\n                .child(\n                    div()\n                        .relative()\n                        .map(|this| match self.size {\n                            Size::XSmall => this.size_3(),\n                            Size::Small => this.size_3p5(),\n                            Size::Medium => this.size_4(),\n                            Size::Large => this.size(rems(1.125)),\n                            _ => this.size_4(),\n                        })\n                        .flex_shrink_0()\n                        .rounded_full()\n                        .border_1()\n                        .border_color(border_color)\n                        .when(cx.theme().shadow && !disabled, |this| this.shadow_xs())\n                        .map(|this| match self.checked {\n                            false => this.bg(cx.theme().input_background()),\n                            _ => this.bg(bg),\n                        })\n                        .child(checkbox_check_icon(\n                            self.id, self.size, checked, disabled, window, cx,\n                        )),\n                )\n                .when(!self.children.is_empty() || self.label.is_some(), |this| {\n                    this.child(\n                        v_flex()\n                            .w_full()\n                            .line_height(relative(1.2))\n                            .gap_1()\n                            .when_some(self.label, |this, label| {\n                                this.child(\n                                    div()\n                                        .size_full()\n                                        .line_height(relative(1.))\n                                        .when(self.disabled, |this| {\n                                            this.text_color(cx.theme().muted_foreground)\n                                        })\n                                        .child(label),\n                                )\n                            })\n                            .children(self.children),\n                    )\n                })\n                .on_mouse_down(gpui::MouseButton::Left, |_, window, _| {\n                    // Avoid focus on mouse down.\n                    window.prevent_default();\n                })\n                .when(!self.disabled, |this| {\n                    this.on_click({\n                        let on_click = self.on_click.clone();\n                        move |_, window, cx| {\n                            window.prevent_default();\n                            Self::handle_click(&on_click, checked, window, cx);\n                        }\n                    })\n                }),\n        )\n    }\n}\n\n/// A Radio group element.\n#[derive(IntoElement)]\npub struct RadioGroup {\n    id: ElementId,\n    style: StyleRefinement,\n    radios: Vec<Radio>,\n    layout: Axis,\n    selected_index: Option<usize>,\n    disabled: bool,\n    on_click: Option<Rc<dyn Fn(&usize, &mut Window, &mut App) + 'static>>,\n}\n\nimpl RadioGroup {\n    fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default().flex_1(),\n            on_click: None,\n            layout: Axis::Vertical,\n            selected_index: None,\n            disabled: false,\n            radios: vec![],\n        }\n    }\n\n    /// Create a new Radio group with default Vertical layout.\n    pub fn vertical(id: impl Into<ElementId>) -> Self {\n        Self::new(id)\n    }\n\n    /// Create a new Radio group with Horizontal layout.\n    pub fn horizontal(id: impl Into<ElementId>) -> Self {\n        Self::new(id).layout(Axis::Horizontal)\n    }\n\n    /// Set the layout of the Radio group. Default is `Axis::Vertical`.\n    pub fn layout(mut self, layout: Axis) -> Self {\n        self.layout = layout;\n        self\n    }\n\n    // Add on_click handler when selected index changes.\n    //\n    // The `&usize` parameter is the selected index.\n    pub fn on_click(mut self, handler: impl Fn(&usize, &mut Window, &mut App) + 'static) -> Self {\n        self.on_click = Some(Rc::new(handler));\n        self\n    }\n\n    /// Set the selected index.\n    pub fn selected_index(mut self, index: Option<usize>) -> Self {\n        self.selected_index = index;\n        self\n    }\n\n    /// Set the disabled state.\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    /// Add a child Radio element.\n    pub fn child(mut self, child: impl Into<Radio>) -> Self {\n        self.radios.push(child.into());\n        self\n    }\n\n    /// Add multiple child Radio elements.\n    pub fn children(mut self, children: impl IntoIterator<Item = impl Into<Radio>>) -> Self {\n        self.radios.extend(children.into_iter().map(Into::into));\n        self\n    }\n}\n\nimpl Styled for RadioGroup {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl From<&'static str> for Radio {\n    fn from(label: &'static str) -> Self {\n        Self::new(label).label(label)\n    }\n}\n\nimpl From<SharedString> for Radio {\n    fn from(label: SharedString) -> Self {\n        Self::new(label.clone()).label(label)\n    }\n}\n\nimpl From<String> for Radio {\n    fn from(label: String) -> Self {\n        Self::new(SharedString::from(label.clone())).label(SharedString::from(label))\n    }\n}\n\nimpl RenderOnce for RadioGroup {\n    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {\n        let on_click = self.on_click;\n        let disabled = self.disabled;\n        let selected_ix = self.selected_index;\n\n        let base = if self.layout.is_vertical() {\n            v_flex()\n        } else {\n            h_flex().w_full().flex_wrap()\n        };\n\n        let mut container = div().id(self.id);\n        *container.style() = self.style;\n\n        container.child(\n            base.gap_3()\n                .children(self.radios.into_iter().enumerate().map(|(ix, mut radio)| {\n                    let checked = selected_ix == Some(ix);\n\n                    radio.id = ix.into();\n                    radio.disabled(disabled).checked(checked).when_some(\n                        on_click.clone(),\n                        |this, on_click| {\n                            this.on_click(move |_, window, cx| {\n                                on_click(&ix, window, cx);\n                            })\n                        },\n                    )\n                })),\n        )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/rating.rs",
    "content": "use crate::theme::ActiveTheme;\nuse crate::{Disableable, Icon, IconName, Sizable, Size, StyledExt, h_flex};\nuse std::rc::Rc;\n\nuse gpui::{\n    App, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce, StyleRefinement,\n    Styled, Window, div, prelude::FluentBuilder as _,\n};\nuse gpui::{ClickEvent, Hsla, StatefulInteractiveElement};\n\n/// A simple star Rating element.\n#[derive(IntoElement)]\npub struct Rating {\n    id: ElementId,\n    style: StyleRefinement,\n    size: Size,\n    disabled: bool,\n    value: usize,\n    max: usize,\n    color: Option<Hsla>,\n    on_click: Option<Rc<dyn Fn(&usize, &mut Window, &mut App) + 'static>>,\n}\n\nimpl Rating {\n    /// Create a new Rating with an `ElementId`.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default(),\n            size: Size::Medium,\n            disabled: false,\n            value: 0,\n            max: 5,\n            color: None,\n            on_click: None,\n        }\n    }\n\n    /// Set the star size.\n    pub fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n\n    /// Disable interaction.\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    /// Set active color, default will use `yellow` from theme colors.\n    pub fn color(mut self, color: impl Into<Hsla>) -> Self {\n        self.color = Some(color.into());\n        self\n    }\n\n    /// Set initial value (0..=max).\n    pub fn value(mut self, value: usize) -> Self {\n        self.value = value;\n        if self.value > self.max {\n            self.value = self.max;\n        }\n        self\n    }\n\n    /// Set maximum number of stars.\n    pub fn max(mut self, max: usize) -> Self {\n        self.max = max;\n        if self.value > self.max {\n            self.value = self.max;\n        }\n        self\n    }\n\n    /// Add on_click handler when the rating changes.\n    ///\n    /// The `&usize` parameter is the new rating value.\n    pub fn on_click(mut self, handler: impl Fn(&usize, &mut Window, &mut App) + 'static) -> Self {\n        self.on_click = Some(Rc::new(handler));\n        self\n    }\n}\n\nimpl Styled for Rating {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Sizable for Rating {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Disableable for Rating {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nstruct RaingState {\n    /// To save the default value on init state, to detect external value changes.\n    default_value: usize,\n    /// To store the current selected value.\n    value: usize,\n    /// To store the currently hovered value.\n    hovered_value: usize,\n}\n\nimpl RenderOnce for Rating {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let id = self.id;\n        let size = self.size;\n        let disabled = self.disabled;\n        let max = self.max;\n        let default_value = self.value;\n        let active_color = self.color.unwrap_or(cx.theme().yellow);\n        let on_click = self.on_click.clone();\n\n        let state = window.use_keyed_state(id.clone(), cx, |_, _| RaingState {\n            default_value,\n            value: default_value,\n            hovered_value: 0,\n        });\n\n        // Reset state if outside has changed `value` prop.\n        if state.read(cx).default_value != default_value {\n            state.update(cx, |state, _| {\n                state.default_value = default_value;\n                state.value = default_value;\n            });\n        }\n        let value = state.read(cx).value;\n\n        h_flex()\n            .id(id)\n            .flex_nowrap()\n            .refine_style(&self.style)\n            .on_hover(window.listener_for(&state, move |state, hovered, _, cx| {\n                if !hovered {\n                    state.hovered_value = 0;\n                    cx.notify();\n                }\n            }))\n            .map(|mut this| {\n                for ix in 1..=max {\n                    let filled = ix <= value;\n                    let hovered = state.read(cx).hovered_value >= ix;\n\n                    this = this.child(\n                        div()\n                            .id(ix)\n                            .p_0p5()\n                            .flex_none()\n                            .flex_shrink_0()\n                            .when(filled || hovered, |this| this.text_color(active_color))\n                            .child(\n                                Icon::new(if filled {\n                                    IconName::StarFill\n                                } else {\n                                    IconName::Star\n                                })\n                                .with_size(size),\n                            )\n                            .when(!disabled, |this| {\n                                this.on_mouse_move(window.listener_for(\n                                    &state,\n                                    move |state, _, _, cx| {\n                                        state.hovered_value = ix;\n                                        cx.notify();\n                                    },\n                                ))\n                                .on_click({\n                                    let state = state.clone();\n                                    let on_click = on_click.clone();\n                                    move |_: &ClickEvent, window, cx| {\n                                        let new = if value >= ix {\n                                            ix.saturating_sub(1)\n                                        } else {\n                                            ix\n                                        };\n\n                                        state.update(cx, |state, cx| {\n                                            state.value = new;\n                                            cx.notify();\n                                        });\n\n                                        if let Some(on_click) = &on_click {\n                                            on_click(&new, window, cx);\n                                        }\n                                    }\n                                })\n                            }),\n                    );\n                }\n\n                this\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/resizable/mod.rs",
    "content": "use std::ops::Range;\n\nuse gpui::{\n    Along, App, Axis, Bounds, Context, ElementId, EventEmitter, IsZero, Pixels, Window, px,\n};\n\nmod panel;\nmod resize_handle;\npub use panel::*;\npub(crate) use resize_handle::*;\n\npub(crate) const PANEL_MIN_SIZE: Pixels = px(100.);\n\n/// Create a [`ResizablePanelGroup`] with horizontal resizing\npub fn h_resizable(id: impl Into<ElementId>) -> ResizablePanelGroup {\n    ResizablePanelGroup::new(id).axis(Axis::Horizontal)\n}\n\n/// Create a [`ResizablePanelGroup`] with vertical resizing\npub fn v_resizable(id: impl Into<ElementId>) -> ResizablePanelGroup {\n    ResizablePanelGroup::new(id).axis(Axis::Vertical)\n}\n\n/// Create a [`ResizablePanel`].\npub fn resizable_panel() -> ResizablePanel {\n    ResizablePanel::new()\n}\n\n/// State for a [`ResizablePanel`]\n#[derive(Debug, Clone)]\npub struct ResizableState {\n    /// The `axis` will sync to actual axis of the ResizablePanelGroup in use.\n    axis: Axis,\n    panels: Vec<ResizablePanelState>,\n    sizes: Vec<Pixels>,\n    pub(crate) resizing_panel_ix: Option<usize>,\n    bounds: Bounds<Pixels>,\n}\n\nimpl Default for ResizableState {\n    fn default() -> Self {\n        Self {\n            axis: Axis::Horizontal,\n            panels: vec![],\n            sizes: vec![],\n            resizing_panel_ix: None,\n            bounds: Bounds::default(),\n        }\n    }\n}\n\nimpl ResizableState {\n    /// Get the size of the panels.\n    pub fn sizes(&self) -> &Vec<Pixels> {\n        &self.sizes\n    }\n\n    pub(crate) fn insert_panel(\n        &mut self,\n        size: Option<Pixels>,\n        ix: Option<usize>,\n        cx: &mut Context<Self>,\n    ) {\n        let panel_state = ResizablePanelState {\n            size,\n            ..Default::default()\n        };\n\n        let size = size.unwrap_or(PANEL_MIN_SIZE);\n\n        // We make sure that the size always sums up to the container size\n        // by reducing the size of all other panels first.\n        let container_size = self.container_size().max(px(1.));\n        let total_leftover_size = (container_size - size).max(px(1.));\n\n        for (i, panel) in self.panels.iter_mut().enumerate() {\n            let ratio = self.sizes[i] / container_size;\n            self.sizes[i] = total_leftover_size * ratio;\n            panel.size = Some(self.sizes[i]);\n        }\n\n        if let Some(ix) = ix {\n            self.panels.insert(ix, panel_state);\n            self.sizes.insert(ix, size);\n        } else {\n            self.panels.push(panel_state);\n            self.sizes.push(size);\n        };\n\n        cx.notify();\n    }\n\n    pub(crate) fn sync_panels_count(\n        &mut self,\n        axis: Axis,\n        panels_count: usize,\n        cx: &mut Context<Self>,\n    ) {\n        let mut changed = self.axis != axis;\n        self.axis = axis;\n\n        if panels_count > self.panels.len() {\n            let diff = panels_count - self.panels.len();\n            self.panels\n                .extend(vec![ResizablePanelState::default(); diff]);\n            self.sizes.extend(vec![PANEL_MIN_SIZE; diff]);\n            changed = true;\n        }\n\n        if panels_count < self.panels.len() {\n            self.panels.truncate(panels_count);\n            self.sizes.truncate(panels_count);\n            changed = true;\n        }\n\n        if changed {\n            // We need to make sure the total size is in line with the container size.\n            self.adjust_to_container_size(cx);\n        }\n    }\n\n    pub(crate) fn update_panel_size(\n        &mut self,\n        panel_ix: usize,\n        bounds: Bounds<Pixels>,\n        size_range: Range<Pixels>,\n        cx: &mut Context<Self>,\n    ) {\n        let size = bounds.size.along(self.axis);\n        // This check is only necessary to stop the very first panel from resizing on its own\n        // it needs to be passed when the panel is freshly created so we get the initial size,\n        // but its also fine when it sometimes passes later.\n        if self.sizes[panel_ix].as_f32() == PANEL_MIN_SIZE.as_f32() {\n            self.sizes[panel_ix] = size;\n            self.panels[panel_ix].size = Some(size);\n        }\n        self.panels[panel_ix].bounds = bounds;\n        self.panels[panel_ix].size_range = size_range;\n        cx.notify();\n    }\n\n    pub(crate) fn remove_panel(&mut self, panel_ix: usize, cx: &mut Context<Self>) {\n        self.panels.remove(panel_ix);\n        self.sizes.remove(panel_ix);\n        if let Some(resizing_panel_ix) = self.resizing_panel_ix {\n            if resizing_panel_ix > panel_ix {\n                self.resizing_panel_ix = Some(resizing_panel_ix - 1);\n            }\n        }\n        self.adjust_to_container_size(cx);\n    }\n\n    pub(crate) fn replace_panel(\n        &mut self,\n        panel_ix: usize,\n        panel: ResizablePanelState,\n        cx: &mut Context<Self>,\n    ) {\n        let old_size = self.sizes[panel_ix];\n\n        self.panels[panel_ix] = panel;\n        self.sizes[panel_ix] = old_size;\n        self.adjust_to_container_size(cx);\n    }\n\n    pub(crate) fn clear(&mut self) {\n        self.panels.clear();\n        self.sizes.clear();\n    }\n\n    #[inline]\n    pub(crate) fn container_size(&self) -> Pixels {\n        self.bounds.size.along(self.axis)\n    }\n\n    pub(crate) fn done_resizing(&mut self, cx: &mut Context<Self>) {\n        self.resizing_panel_ix = None;\n        cx.emit(ResizablePanelEvent::Resized);\n    }\n\n    fn panel_size_range(&self, ix: usize) -> Range<Pixels> {\n        let Some(panel) = self.panels.get(ix) else {\n            return PANEL_MIN_SIZE..Pixels::MAX;\n        };\n\n        panel.size_range.clone()\n    }\n\n    fn sync_real_panel_sizes(&mut self, _: &App) {\n        for (i, panel) in self.panels.iter().enumerate() {\n            self.sizes[i] = panel.bounds.size.along(self.axis);\n        }\n    }\n\n    /// The `ix`` is the index of the panel to resize,\n    /// and the `size` is the new size for the panel.\n    fn resize_panel(&mut self, ix: usize, size: Pixels, _: &mut Window, cx: &mut Context<Self>) {\n        let old_sizes = self.sizes.clone();\n\n        let mut ix = ix;\n        // Only resize the left panels.\n        if ix >= old_sizes.len() - 1 {\n            return;\n        }\n        let container_size = self.container_size();\n        self.sync_real_panel_sizes(cx);\n\n        let move_changed = size - old_sizes[ix];\n        if move_changed == px(0.) {\n            return;\n        }\n\n        let size_range = self.panel_size_range(ix);\n        let new_size = size.clamp(size_range.start, size_range.end);\n        let is_expand = move_changed > px(0.);\n\n        let main_ix = ix;\n        let mut new_sizes = old_sizes.clone();\n\n        if is_expand {\n            let mut changed = new_size - old_sizes[ix];\n            new_sizes[ix] = new_size;\n\n            while changed > px(0.) && ix < old_sizes.len() - 1 {\n                ix += 1;\n                let size_range = self.panel_size_range(ix);\n                let available_size = (new_sizes[ix] - size_range.start).max(px(0.));\n                let to_reduce = changed.min(available_size);\n                new_sizes[ix] -= to_reduce;\n                changed -= to_reduce;\n            }\n        } else {\n            let mut changed = new_size - size;\n            new_sizes[ix] = new_size;\n\n            while changed > px(0.) && ix > 0 {\n                ix -= 1;\n                let size_range = self.panel_size_range(ix);\n                let available_size = (new_sizes[ix] - size_range.start).max(px(0.));\n                let to_reduce = changed.min(available_size);\n                changed -= to_reduce;\n                new_sizes[ix] -= to_reduce;\n            }\n\n            new_sizes[main_ix + 1] += old_sizes[main_ix] - size - changed;\n        }\n\n        // If total size exceeds container size, adjust the main panel\n        let total_size: Pixels = new_sizes.iter().map(|s| s.as_f32()).sum::<f32>().into();\n        if total_size > container_size {\n            let overflow = total_size - container_size;\n            new_sizes[main_ix] = (new_sizes[main_ix] - overflow).max(size_range.start);\n        }\n\n        for (i, _) in old_sizes.iter().enumerate() {\n            let size = new_sizes[i];\n            self.panels[i].size = Some(size);\n        }\n        self.sizes = new_sizes;\n        cx.notify();\n    }\n\n    /// Adjust panel sizes according to the container size.\n    ///\n    /// When the container size changes, the panels should take up the same percentage as they did before.\n    fn adjust_to_container_size(&mut self, cx: &mut Context<Self>) {\n        if self.container_size().is_zero() {\n            return;\n        }\n\n        let container_size = self.container_size();\n        let total_size = px(self.sizes.iter().map(|s| s.as_f32()).sum::<f32>());\n\n        for i in 0..self.panels.len() {\n            let size = self.sizes[i];\n            let ratio = size / total_size;\n            let new_size = container_size * ratio;\n\n            self.sizes[i] = new_size;\n            self.panels[i].size = Some(new_size);\n        }\n        cx.notify();\n    }\n}\n\nimpl EventEmitter<ResizablePanelEvent> for ResizableState {}\n\n#[derive(Debug, Clone, Default)]\npub(crate) struct ResizablePanelState {\n    pub size: Option<Pixels>,\n    pub size_range: Range<Pixels>,\n    bounds: Bounds<Pixels>,\n}\n"
  },
  {
    "path": "crates/ui/src/resizable/panel.rs",
    "content": "use std::{\n    ops::{Deref, Range},\n    rc::Rc,\n};\n\nuse gpui::{\n    Along, AnyElement, App, AppContext, Axis, Bounds, Context, Element, ElementId, Empty, Entity,\n    EventEmitter, InteractiveElement as _, IntoElement, IsZero as _, MouseMoveEvent, MouseUpEvent,\n    ParentElement, Pixels, Render, RenderOnce, Style, Styled, Window, div, prelude::FluentBuilder,\n};\n\nuse crate::{AxisExt, ElementExt, h_flex, resizable::PANEL_MIN_SIZE, v_flex};\n\nuse super::{ResizableState, resizable_panel, resize_handle};\n\npub enum ResizablePanelEvent {\n    Resized,\n}\n\n#[derive(Clone)]\npub(crate) struct DragPanel;\nimpl Render for DragPanel {\n    fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {\n        Empty\n    }\n}\n\n/// A group of resizable panels.\n#[derive(IntoElement)]\npub struct ResizablePanelGroup {\n    id: ElementId,\n    state: Option<Entity<ResizableState>>,\n    axis: Axis,\n    size: Option<Pixels>,\n    children: Vec<ResizablePanel>,\n    on_resize: Rc<dyn Fn(&Entity<ResizableState>, &mut Window, &mut App)>,\n}\n\nimpl ResizablePanelGroup {\n    /// Create a new resizable panel group.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            axis: Axis::Horizontal,\n            children: vec![],\n            state: None,\n            size: None,\n            on_resize: Rc::new(|_, _, _| {}),\n        }\n    }\n\n    /// Bind yourself to a resizable state entity.\n    ///\n    /// If not provided, it will handle its own state internally.\n    pub fn with_state(mut self, state: &Entity<ResizableState>) -> Self {\n        self.state = Some(state.clone());\n        self\n    }\n\n    /// Set the axis of the resizable panel group, default is horizontal.\n    pub fn axis(mut self, axis: Axis) -> Self {\n        self.axis = axis;\n        self\n    }\n\n    /// Add a panel to the group.\n    ///\n    /// - The `axis` will be set to the same axis as the group.\n    /// - The `initial_size` will be set to the average size of all panels if not provided.\n    /// - The `group` will be set to the group entity.\n    pub fn child(mut self, panel: impl Into<ResizablePanel>) -> Self {\n        self.children.push(panel.into());\n        self\n    }\n\n    /// Add multiple panels to the group.\n    pub fn children<I>(mut self, panels: impl IntoIterator<Item = I>) -> Self\n    where\n        I: Into<ResizablePanel>,\n    {\n        self.children = panels.into_iter().map(|panel| panel.into()).collect();\n        self\n    }\n\n    /// Set size of the resizable panel group\n    ///\n    /// - When the axis is horizontal, the size is the height of the group.\n    /// - When the axis is vertical, the size is the width of the group.\n    pub fn size(mut self, size: Pixels) -> Self {\n        self.size = Some(size);\n        self\n    }\n\n    /// Set the callback to be called when the panels are resized.\n    ///\n    /// ## Callback arguments\n    ///\n    /// - Entity<ResizableState>: The state of the ResizablePanelGroup.\n    pub fn on_resize(\n        mut self,\n        on_resize: impl Fn(&Entity<ResizableState>, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_resize = Rc::new(on_resize);\n        self\n    }\n}\n\nimpl<T> From<T> for ResizablePanel\nwhere\n    T: Into<AnyElement>,\n{\n    fn from(value: T) -> Self {\n        resizable_panel().child(value.into())\n    }\n}\n\nimpl From<ResizablePanelGroup> for ResizablePanel {\n    fn from(value: ResizablePanelGroup) -> Self {\n        resizable_panel().child(value)\n    }\n}\n\nimpl EventEmitter<ResizablePanelEvent> for ResizablePanelGroup {}\n\nimpl RenderOnce for ResizablePanelGroup {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let state = self.state.unwrap_or(\n            window.use_keyed_state(self.id.clone(), cx, |_, _| ResizableState::default()),\n        );\n        let container = if self.axis.is_horizontal() {\n            h_flex()\n        } else {\n            v_flex()\n        };\n\n        // Sync panels to the state\n        let panels_count = self.children.len();\n        state.update(cx, |state, cx| {\n            state.sync_panels_count(self.axis, panels_count, cx);\n        });\n\n        container\n            .id(self.id)\n            .size_full()\n            .children(\n                self.children\n                    .into_iter()\n                    .enumerate()\n                    .map(|(ix, mut panel)| {\n                        panel.panel_ix = ix;\n                        panel.axis = self.axis;\n                        panel.state = Some(state.clone());\n                        panel\n                    }),\n            )\n            .on_prepaint({\n                let state = state.clone();\n                move |bounds, _, cx| {\n                    state.update(cx, |state, cx| {\n                        let size_changed =\n                            state.bounds.size.along(self.axis) != bounds.size.along(self.axis);\n\n                        state.bounds = bounds;\n\n                        if size_changed {\n                            state.adjust_to_container_size(cx);\n                        }\n                    })\n                }\n            })\n            .child(ResizePanelGroupElement {\n                state: state.clone(),\n                axis: self.axis,\n                on_resize: self.on_resize.clone(),\n            })\n    }\n}\n\n/// A resizable panel inside a [`ResizablePanelGroup`].\n#[derive(IntoElement)]\npub struct ResizablePanel {\n    axis: Axis,\n    panel_ix: usize,\n    state: Option<Entity<ResizableState>>,\n    /// Initial size is the size that the panel has when it is created.\n    initial_size: Option<Pixels>,\n    /// size range limit of this panel.\n    size_range: Range<Pixels>,\n    children: Vec<AnyElement>,\n    visible: bool,\n}\n\nimpl ResizablePanel {\n    /// Create a new resizable panel.\n    pub(super) fn new() -> Self {\n        Self {\n            panel_ix: 0,\n            initial_size: None,\n            state: None,\n            size_range: (PANEL_MIN_SIZE..Pixels::MAX),\n            axis: Axis::Horizontal,\n            children: vec![],\n            visible: true,\n        }\n    }\n\n    /// Set the visibility of the panel, default is true.\n    pub fn visible(mut self, visible: bool) -> Self {\n        self.visible = visible;\n        self\n    }\n\n    /// Set the initial size of the panel.\n    pub fn size(mut self, size: impl Into<Pixels>) -> Self {\n        self.initial_size = Some(size.into());\n        self\n    }\n\n    /// Set the size range to limit panel resize.\n    ///\n    /// Default is [`PANEL_MIN_SIZE`] to [`Pixels::MAX`].\n    pub fn size_range(mut self, range: impl Into<Range<Pixels>>) -> Self {\n        self.size_range = range.into();\n        self\n    }\n}\n\nimpl ParentElement for ResizablePanel {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl RenderOnce for ResizablePanel {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        if !self.visible {\n            return div().id((\"resizable-panel\", self.panel_ix));\n        }\n\n        let state = self\n            .state\n            .expect(\"BUG: The `state` in ResizablePanel should be present.\");\n        let panel_state = state\n            .read(cx)\n            .panels\n            .get(self.panel_ix)\n            .expect(\"BUG: The `index` of ResizablePanel should be one of in `state`.\");\n        let size_range = self.size_range.clone();\n\n        div()\n            .id((\"resizable-panel\", self.panel_ix))\n            .flex()\n            .flex_grow()\n            .size_full()\n            .relative()\n            .when(self.axis.is_vertical(), |this| {\n                this.min_h(size_range.start).max_h(size_range.end)\n            })\n            .when(self.axis.is_horizontal(), |this| {\n                this.min_w(size_range.start).max_w(size_range.end)\n            })\n            // 1. initial_size is None, to use auto size.\n            // 2. initial_size is Some and size is none, to use the initial size of the panel for first time render.\n            // 3. initial_size is Some and size is Some, use `size`.\n            .when(self.initial_size.is_none(), |this| this.flex_shrink())\n            .when_some(self.initial_size, |this, initial_size| {\n                // The `self.size` is None, that mean the initial size for the panel,\n                // so we need set `flex_shrink_0` To let it keep the initial size.\n                this.when(\n                    panel_state.size.is_none() && !initial_size.is_zero(),\n                    |this| this.flex_none(),\n                )\n                .flex_basis(initial_size)\n            })\n            .map(|this| match panel_state.size {\n                Some(size) => this.flex_basis(size.min(size_range.end).max(size_range.start)),\n                None => this,\n            })\n            .on_prepaint({\n                let state = state.clone();\n                move |bounds, _, cx| {\n                    state.update(cx, |state, cx| {\n                        state.update_panel_size(self.panel_ix, bounds, self.size_range, cx)\n                    })\n                }\n            })\n            .child(\n                // Here add a wrapper `div` to avoid the content may not fill full issue.\n                //\n                // Ref:\n                // https://github.com/longbridge/gpui-component/pull/2097\n                div().size_full().children(self.children),\n            )\n            .when(self.panel_ix > 0, |this| {\n                let ix = self.panel_ix - 1;\n                this.child(resize_handle((\"resizable-handle\", ix), self.axis).on_drag(\n                    DragPanel,\n                    move |drag_panel, _, _, cx| {\n                        cx.stop_propagation();\n                        // Set current resizing panel ix\n                        state.update(cx, |state, _| {\n                            state.resizing_panel_ix = Some(ix);\n                        });\n                        cx.new(|_| drag_panel.deref().clone())\n                    },\n                ))\n            })\n    }\n}\n\nstruct ResizePanelGroupElement {\n    state: Entity<ResizableState>,\n    on_resize: Rc<dyn Fn(&Entity<ResizableState>, &mut Window, &mut App)>,\n    axis: Axis,\n}\n\nimpl IntoElement for ResizePanelGroupElement {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\nimpl Element for ResizePanelGroupElement {\n    type RequestLayoutState = ();\n    type PrepaintState = ();\n\n    fn id(&self) -> Option<gpui::ElementId> {\n        None\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        _: Option<&gpui::GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (gpui::LayoutId, Self::RequestLayoutState) {\n        (window.request_layout(Style::default(), None, cx), ())\n    }\n\n    fn prepaint(\n        &mut self,\n        _: Option<&gpui::GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        _: Bounds<Pixels>,\n        _: &mut Self::RequestLayoutState,\n        _window: &mut Window,\n        _cx: &mut App,\n    ) -> Self::PrepaintState {\n        ()\n    }\n\n    fn paint(\n        &mut self,\n        _: Option<&gpui::GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        _: Bounds<Pixels>,\n        _: &mut Self::RequestLayoutState,\n        _: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        window.on_mouse_event({\n            let state = self.state.clone();\n            let axis = self.axis;\n            let current_ix = state.read(cx).resizing_panel_ix;\n            move |e: &MouseMoveEvent, phase, window, cx| {\n                if !phase.bubble() {\n                    return;\n                }\n                let Some(ix) = current_ix else { return };\n\n                state.update(cx, |state, cx| {\n                    let panel = state.panels.get(ix).expect(\"BUG: invalid panel index\");\n\n                    match axis {\n                        Axis::Horizontal => {\n                            state.resize_panel(ix, e.position.x - panel.bounds.left(), window, cx)\n                        }\n                        Axis::Vertical => {\n                            state.resize_panel(ix, e.position.y - panel.bounds.top(), window, cx);\n                        }\n                    }\n                    cx.notify();\n                })\n            }\n        });\n\n        // When any mouse up, stop dragging\n        window.on_mouse_event({\n            let state = self.state.clone();\n            let current_ix = state.read(cx).resizing_panel_ix;\n            let on_resize = self.on_resize.clone();\n            move |_: &MouseUpEvent, phase, window, cx| {\n                if current_ix.is_none() {\n                    return;\n                }\n                if phase.bubble() {\n                    state.update(cx, |state, cx| state.done_resizing(cx));\n                    on_resize(&state, window, cx);\n                }\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/resizable/resize_handle.rs",
    "content": "use std::{cell::Cell, rc::Rc};\n\nuse gpui::{\n    div, prelude::FluentBuilder as _, px, AnyElement, App, Axis, Element, ElementId, Entity,\n    GlobalElementId, InteractiveElement, IntoElement, MouseDownEvent, MouseUpEvent,\n    ParentElement as _, Pixels, Point, Render, StatefulInteractiveElement, Styled as _, Window,\n};\n\nuse crate::{dock::DockPlacement, ActiveTheme as _, AxisExt as _};\n\npub(crate) const HANDLE_PADDING: Pixels = px(4.);\npub(crate) const HANDLE_SIZE: Pixels = px(1.);\n\n/// Create a resize handle for a resizable panel.\npub(crate) fn resize_handle<T: 'static, E: 'static + Render>(\n    id: impl Into<ElementId>,\n    axis: Axis,\n) -> ResizeHandle<T, E> {\n    ResizeHandle::new(id, axis)\n}\n\npub(crate) struct ResizeHandle<T: 'static, E: 'static + Render> {\n    id: ElementId,\n    axis: Axis,\n    drag_value: Option<Rc<T>>,\n    placement: Option<DockPlacement>,\n    on_drag: Option<Rc<dyn Fn(&Point<Pixels>, &mut Window, &mut App) -> Entity<E>>>,\n}\n\nimpl<T: 'static, E: 'static + Render> ResizeHandle<T, E> {\n    fn new(id: impl Into<ElementId>, axis: Axis) -> Self {\n        let id = id.into();\n        Self {\n            id: id.clone(),\n            on_drag: None,\n            drag_value: None,\n            placement: None,\n            axis,\n        }\n    }\n\n    pub(crate) fn on_drag(\n        mut self,\n        value: T,\n        f: impl Fn(Rc<T>, &Point<Pixels>, &mut Window, &mut App) -> Entity<E> + 'static,\n    ) -> Self {\n        let value = Rc::new(value);\n        self.drag_value = Some(value.clone());\n        self.on_drag = Some(Rc::new(move |p, window, cx| {\n            f(value.clone(), p, window, cx)\n        }));\n        self\n    }\n\n    pub(crate) fn placement(mut self, placement: DockPlacement) -> Self {\n        self.placement = Some(placement);\n        self\n    }\n}\n\n#[derive(Default, Debug, Clone)]\nstruct ResizeHandleState {\n    active: Cell<bool>,\n}\n\nimpl ResizeHandleState {\n    fn set_active(&self, active: bool) {\n        self.active.set(active);\n    }\n\n    fn is_active(&self) -> bool {\n        self.active.get()\n    }\n}\n\nimpl<T: 'static, E: 'static + Render> IntoElement for ResizeHandle<T, E> {\n    type Element = ResizeHandle<T, E>;\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\nimpl<T: 'static, E: 'static + Render> Element for ResizeHandle<T, E> {\n    type RequestLayoutState = AnyElement;\n    type PrepaintState = ();\n\n    fn id(&self) -> Option<ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        id: Option<&GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (gpui::LayoutId, Self::RequestLayoutState) {\n        let neg_offset = -HANDLE_PADDING;\n        let axis = self.axis;\n\n        window.with_element_state(id.unwrap(), |state, window| {\n            let state = state.unwrap_or(ResizeHandleState::default());\n\n            let bg_color = if state.is_active() {\n                cx.theme().drag_border\n            } else {\n                cx.theme().border\n            };\n\n            let mut el = div()\n                .id(self.id.clone())\n                .occlude()\n                .absolute()\n                .flex_shrink_0()\n                .group(\"handle\")\n                .when_some(self.on_drag.clone(), |this, on_drag| {\n                    this.on_drag(\n                        self.drag_value.clone().unwrap(),\n                        move |_, position, window, cx| on_drag(&position, window, cx),\n                    )\n                })\n                .map(|this| match self.placement {\n                    Some(DockPlacement::Left) => {\n                        // Special for Left Dock\n                        //  FIXME: Improve this to let the scroll bar have px(HANDLE_PADDING)\n                        this.cursor_col_resize()\n                            .top_0()\n                            .right(px(1.))\n                            .h_full()\n                            .w(HANDLE_SIZE)\n                            .pl(HANDLE_PADDING)\n                    }\n                    _ => this\n                        .when(axis.is_horizontal(), |this| {\n                            this.cursor_col_resize()\n                                .top_0()\n                                .left(neg_offset)\n                                .h_full()\n                                .w(HANDLE_SIZE)\n                                .px(HANDLE_PADDING)\n                        })\n                        .when(axis.is_vertical(), |this| {\n                            this.cursor_row_resize()\n                                .top(neg_offset)\n                                .left_0()\n                                .w_full()\n                                .h(HANDLE_SIZE)\n                                .py(HANDLE_PADDING)\n                        }),\n                })\n                .child(\n                    div()\n                        .bg(bg_color)\n                        .group_hover(\"handle\", |this| this.bg(bg_color))\n                        .when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE))\n                        .when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)),\n                )\n                .into_any_element();\n\n            let layout_id = el.request_layout(window, cx);\n\n            ((layout_id, el), state)\n        })\n    }\n\n    fn prepaint(\n        &mut self,\n        _: Option<&GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        _: gpui::Bounds<Pixels>,\n        request_layout: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::PrepaintState {\n        request_layout.prepaint(window, cx);\n    }\n\n    fn paint(\n        &mut self,\n        id: Option<&GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        bounds: gpui::Bounds<Pixels>,\n        request_layout: &mut Self::RequestLayoutState,\n        _: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        request_layout.paint(window, cx);\n\n        window.with_element_state(id.unwrap(), |state: Option<ResizeHandleState>, window| {\n            let state = state.unwrap_or(ResizeHandleState::default());\n\n            window.on_mouse_event({\n                let state = state.clone();\n                move |ev: &MouseDownEvent, phase, window, _| {\n                    if bounds.contains(&ev.position) && phase.bubble() {\n                        state.set_active(true);\n                        window.refresh();\n                    }\n                }\n            });\n\n            window.on_mouse_event({\n                let state = state.clone();\n                move |_: &MouseUpEvent, _, window, _| {\n                    if state.is_active() {\n                        state.set_active(false);\n                        window.refresh();\n                    }\n                }\n            });\n\n            ((), state)\n        });\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/root.rs",
    "content": "use crate::{\n    ActiveTheme, Anchor, ElementExt, Placement, StyledExt,\n    dialog::{ANIMATION_DURATION, Dialog},\n    focus_trap::FocusTrapManager,\n    input::InputState,\n    notification::{Notification, NotificationList},\n    sheet::Sheet,\n    window_border,\n};\nuse gpui::{\n    AnyView, App, AppContext, Context, DefiniteLength, Entity, FocusHandle, InteractiveElement,\n    IntoElement, KeyBinding, ParentElement as _, Pixels, Render, StyleRefinement, Styled,\n    WeakFocusHandle, Window, actions, div, prelude::FluentBuilder as _,\n};\nuse std::{any::TypeId, rc::Rc};\n\nactions!(root, [Tab, TabPrev]);\n\nconst CONTEXT: &str = \"Root\";\npub(crate) fn init(cx: &mut App) {\n    cx.bind_keys([\n        KeyBinding::new(\"tab\", Tab, Some(CONTEXT)),\n        KeyBinding::new(\"shift-tab\", TabPrev, Some(CONTEXT)),\n    ]);\n}\n\n/// Root is a view for the App window for as the top level view (Must be the first view in the window).\n///\n/// It is used to manage the Sheet, Dialog, and Notification.\npub struct Root {\n    style: StyleRefinement,\n    view: AnyView,\n    pub(crate) active_sheet: Option<ActiveSheet>,\n    pub(crate) active_dialogs: Vec<ActiveDialog>,\n    pub(super) focused_input: Option<Entity<InputState>>,\n    pub notification: Entity<NotificationList>,\n    sheet_size: Option<DefiniteLength>,\n    window_shadow_size: Pixels,\n    /// The focus handle that will be restored after a dialog is closed with animation.\n    /// Used to handle rapid dialog opening/closing to maintain correct focus chain.\n    pending_focus_restore: Option<WeakFocusHandle>,\n}\n\n#[derive(Clone)]\npub(crate) struct ActiveSheet {\n    focus_handle: FocusHandle,\n    /// The previous focused handle before opening the Sheet.\n    previous_focused_handle: Option<WeakFocusHandle>,\n    placement: Placement,\n    builder: Rc<dyn Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static>,\n}\n\n#[derive(Clone)]\npub(crate) struct ActiveDialog {\n    focus_handle: FocusHandle,\n    /// The previous focused handle before opening the Dialog.\n    previous_focused_handle: Option<WeakFocusHandle>,\n    builder: Rc<dyn Fn(Dialog, &mut Window, &mut App) -> Dialog + 'static>,\n}\n\nimpl ActiveDialog {\n    pub(crate) fn new(\n        focus_handle: FocusHandle,\n        previous_focused_handle: Option<WeakFocusHandle>,\n        builder: impl Fn(Dialog, &mut Window, &mut App) -> Dialog + 'static,\n    ) -> Self {\n        Self {\n            focus_handle,\n            previous_focused_handle,\n            builder: Rc::new(builder),\n        }\n    }\n}\n\nimpl Root {\n    /// Create a new Root view.\n    pub fn new(view: impl Into<AnyView>, window: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            view: view.into(),\n            active_sheet: None,\n            active_dialogs: Vec::new(),\n            focused_input: None,\n            notification: cx.new(|cx| NotificationList::new(window, cx)),\n            sheet_size: None,\n            window_shadow_size: window_border::SHADOW_SIZE,\n            pending_focus_restore: None,\n        }\n    }\n\n    /// Set the window border shadow size for Linux client-side decorations.\n    ///\n    /// Default: [`window_border::SHADOW_SIZE`]\n    pub fn window_shadow_size(mut self, size: impl Into<Pixels>) -> Self {\n        self.window_shadow_size = size.into();\n        self\n    }\n\n    pub fn update<F, R>(window: &mut Window, cx: &mut App, f: F) -> R\n    where\n        F: FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,\n    {\n        let root = window\n            .root::<Root>()\n            .flatten()\n            .expect(\"BUG: window first layer should be a gpui_component::Root.\");\n\n        root.update(cx, |root, cx| f(root, window, cx))\n    }\n\n    pub fn read<'a>(window: &'a Window, cx: &'a App) -> &'a Self {\n        &window\n            .root::<Root>()\n            .expect(\"The window root view should be of type `ui::Root`.\")\n            .unwrap()\n            .read(cx)\n    }\n\n    // Render Notification layer.\n    pub fn render_notification_layer(\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Option<impl IntoElement + use<>> {\n        let root = window.root::<Root>()??;\n\n        let active_sheet_placement = root.read(cx).active_sheet.clone().map(|d| d.placement);\n\n        let sheet_size = root.read(cx).sheet_size;\n        let (mt, mr, mb, ml) = match active_sheet_placement {\n            Some(Placement::Top) => (sheet_size, None, None, None),\n            Some(Placement::Right) => (None, sheet_size, None, None),\n            Some(Placement::Bottom) => (None, None, sheet_size, None),\n            Some(Placement::Left) => (None, None, None, sheet_size),\n            _ => (None, None, None, None),\n        };\n\n        let placement = cx.theme().notification.placement;\n\n        Some(\n            div()\n                .absolute()\n                .when(matches!(placement, Anchor::TopRight), |this| {\n                    this.top_0().right_0()\n                })\n                .when(matches!(placement, Anchor::TopLeft), |this| {\n                    this.top_0().left_0()\n                })\n                .when(matches!(placement, Anchor::TopCenter), |this| {\n                    this.top_0().mx_auto()\n                })\n                .when(matches!(placement, Anchor::BottomRight), |this| {\n                    this.bottom_0().right_0()\n                })\n                .when(matches!(placement, Anchor::BottomLeft), |this| {\n                    this.bottom_0().left_0()\n                })\n                .when(matches!(placement, Anchor::BottomCenter), |this| {\n                    this.bottom_0().mx_auto()\n                })\n                .when_some(mt, |this, offset| this.mt(offset))\n                .when_some(mr, |this, offset| this.mr(offset))\n                .when_some(mb, |this, offset| this.mb(offset))\n                .when_some(ml, |this, offset| this.ml(offset))\n                .child(root.read(cx).notification.clone()),\n        )\n    }\n\n    /// Render the Sheet layer.\n    pub fn render_sheet_layer(\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Option<impl IntoElement + use<>> {\n        let root = window.root::<Root>()??;\n\n        if let Some(active_sheet) = root.read(cx).active_sheet.clone() {\n            let mut sheet = Sheet::new(window, cx);\n            sheet = (active_sheet.builder)(sheet, window, cx);\n            sheet.focus_handle = active_sheet.focus_handle.clone();\n            sheet.placement = active_sheet.placement;\n\n            let size = sheet.size;\n\n            return Some(\n                div()\n                    .relative()\n                    .child(sheet)\n                    .on_prepaint(move |_, _, cx| root.update(cx, |r, _| r.sheet_size = Some(size))),\n            );\n        }\n\n        None\n    }\n\n    /// Render the Dialog layer.\n    pub fn render_dialog_layer(\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Option<impl IntoElement + use<>> {\n        let root = window.root::<Root>()??;\n\n        let active_dialogs = root.read(cx).active_dialogs.clone();\n\n        if active_dialogs.is_empty() {\n            return None;\n        }\n\n        let mut show_overlay_ix = None;\n\n        let mut dialogs = active_dialogs\n            .iter()\n            .enumerate()\n            .map(|(i, active_dialog)| {\n                let mut dialog = Dialog::new(cx);\n\n                dialog = (active_dialog.builder)(dialog, window, cx);\n\n                // Give the dialog the focus handle, because `dialog` is a temporary value, is not possible to\n                // keep the focus handle in the dialog.\n                //\n                // So we keep the focus handle in the `active_dialog`, this is owned by the `Root`.\n                dialog.focus_handle = active_dialog.focus_handle.clone();\n\n                dialog.layer_ix = i;\n                // Find the dialog which one needs to show overlay.\n                if dialog.has_overlay() {\n                    show_overlay_ix = Some(i);\n                }\n\n                dialog\n            })\n            .collect::<Vec<_>>();\n\n        if let Some(ix) = show_overlay_ix {\n            if let Some(dialog) = dialogs.get_mut(ix) {\n                dialog.props.overlay_visible = true;\n            }\n        }\n\n        Some(div().children(dialogs))\n    }\n\n    pub fn open_dialog<F>(&mut self, build: F, window: &mut Window, cx: &mut Context<'_, Root>)\n    where\n        F: Fn(Dialog, &mut Window, &mut App) -> Dialog + 'static,\n    {\n        let mut previous_focused_handle = window.focused(cx).map(|h| h.downgrade());\n\n        // Use pending focus restore if available to maintain correct focus chain\n        // when a new dialog is opened immediately after closing another dialog.\n        if let Some(pending_handle) = self.pending_focus_restore.take() {\n            previous_focused_handle = Some(pending_handle);\n        }\n\n        let focus_handle = cx.focus_handle();\n        focus_handle.focus(window, cx);\n\n        self.active_dialogs.push(ActiveDialog::new(\n            focus_handle,\n            previous_focused_handle,\n            build,\n        ));\n        cx.notify();\n    }\n\n    fn close_dialog_internal(&mut self) -> Option<FocusHandle> {\n        self.focused_input = None;\n        self.active_dialogs\n            .pop()\n            .and_then(|d| d.previous_focused_handle)\n            .and_then(|h| h.upgrade())\n    }\n\n    pub fn close_dialog(&mut self, window: &mut Window, cx: &mut Context<'_, Root>) {\n        if let Some(handle) = self.close_dialog_internal() {\n            window.focus(&handle, cx);\n        }\n        cx.notify();\n    }\n\n    pub(crate) fn defer_close_dialog(&mut self, window: &mut Window, cx: &mut Context<'_, Root>) {\n        if let Some(handle) = self.close_dialog_internal() {\n            let dialogs_count = self.active_dialogs.len();\n\n            // Save for new dialogs opened during animation to maintain focus chain\n            self.pending_focus_restore = Some(handle.downgrade());\n\n            cx.spawn_in(window, async move |this, cx| {\n                cx.background_executor().timer(*ANIMATION_DURATION).await;\n                let _ = this.update_in(cx, |this, window, cx| {\n                    let current_dialogs_count = this.active_dialogs.len();\n                    // Only restore focus if no new dialogs were opened during animation\n                    if current_dialogs_count == dialogs_count {\n                        window.focus(&handle, cx);\n                    }\n                    this.pending_focus_restore = None;\n                });\n            })\n            .detach();\n        }\n        cx.notify();\n    }\n\n    pub fn close_all_dialogs(&mut self, window: &mut Window, cx: &mut Context<'_, Root>) {\n        self.focused_input = None;\n        let previous_focused_handle = self\n            .active_dialogs\n            .first()\n            .and_then(|d| d.previous_focused_handle.clone());\n        self.active_dialogs.clear();\n        if let Some(handle) = previous_focused_handle.and_then(|h| h.upgrade()) {\n            window.focus(&handle, cx);\n        }\n        cx.notify();\n    }\n\n    pub fn open_sheet_at<F>(\n        &mut self,\n        placement: Placement,\n        build: F,\n        window: &mut Window,\n        cx: &mut Context<'_, Root>,\n    ) where\n        F: Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static,\n    {\n        let previous_focused_handle = self\n            .active_sheet\n            .take()\n            .and_then(|s| s.previous_focused_handle)\n            .or_else(|| window.focused(cx).map(|h| h.downgrade()));\n\n        let focus_handle = cx.focus_handle();\n        focus_handle.focus(window, cx);\n        self.active_sheet = Some(ActiveSheet {\n            focus_handle,\n            previous_focused_handle,\n            placement,\n            builder: Rc::new(build),\n        });\n        cx.notify();\n    }\n\n    pub fn close_sheet(&mut self, window: &mut Window, cx: &mut Context<'_, Root>) {\n        self.focused_input = None;\n        if let Some(previous_handle) = self\n            .active_sheet\n            .as_ref()\n            .and_then(|s| s.previous_focused_handle.as_ref())\n            .and_then(|h| h.upgrade())\n        {\n            window.focus(&previous_handle, cx);\n        }\n        self.active_sheet = None;\n        cx.notify();\n    }\n\n    pub fn push_notification(\n        &mut self,\n        note: impl Into<Notification>,\n        window: &mut Window,\n        cx: &mut Context<'_, Root>,\n    ) {\n        self.notification\n            .update(cx, |view, cx| view.push(note, window, cx));\n        cx.notify();\n    }\n\n    pub fn remove_notification<T: Sized + 'static>(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<'_, Root>,\n    ) {\n        self.notification.update(cx, |view, cx| {\n            let id = TypeId::of::<T>();\n            view.close(id, window, cx);\n        });\n        cx.notify();\n    }\n\n    pub fn clear_notifications(&mut self, window: &mut Window, cx: &mut Context<'_, Root>) {\n        self.notification\n            .update(cx, |view, cx| view.clear(window, cx));\n        cx.notify();\n    }\n\n    /// Return the root view of the Root.\n    pub fn view(&self) -> &AnyView {\n        &self.view\n    }\n\n    fn on_action_tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {\n        // Check if we're inside a focus trap\n        if let Some(container_focus_handle) = FocusTrapManager::find_active_trap(window, cx) {\n            // We're in a focus trap - try to focus next, then check if we're still inside\n            let before_focus = window.focused(cx);\n\n            // Try normal focus navigation\n            window.focus_next(cx);\n\n            // Check if we're still in the trap\n            if !container_focus_handle.contains_focused(window, cx) {\n                // We jumped out of the trap - need to cycle back to the beginning\n                // Find the first focusable element in the trap by continuing to focus_next\n                let mut attempts = 0;\n                const MAX_ATTEMPTS: usize = 100; // Prevent infinite loop\n\n                while !container_focus_handle.contains_focused(window, cx)\n                    && attempts < MAX_ATTEMPTS\n                {\n                    window.focus_next(cx);\n                    attempts += 1;\n\n                    // If we cycled back to where we started, restore original focus\n                    if window.focused(cx) == before_focus {\n                        break;\n                    }\n                }\n            }\n            return;\n        }\n\n        // Normal tab navigation\n        window.focus_next(cx);\n    }\n\n    fn on_action_tab_prev(&mut self, _: &TabPrev, window: &mut Window, cx: &mut Context<Self>) {\n        // Check if we're inside a focus trap\n        if let Some(container_focus_handle) = FocusTrapManager::find_active_trap(window, cx) {\n            // We're in a focus trap - try to focus previous, then check if we're still inside\n            let before_focus = window.focused(cx);\n\n            // Try normal focus navigation\n            window.focus_prev(cx);\n\n            // Check if we're still in the trap\n            if !container_focus_handle.contains_focused(window, cx) {\n                // We jumped out of the trap - need to cycle back to the end\n                // Find the last focusable element in the trap by continuing to focus_prev\n                let mut attempts = 0;\n                const MAX_ATTEMPTS: usize = 100; // Prevent infinite loop\n\n                while !container_focus_handle.contains_focused(window, cx)\n                    && attempts < MAX_ATTEMPTS\n                {\n                    window.focus_prev(cx);\n                    attempts += 1;\n\n                    // If we cycled back to where we started, restore original focus\n                    if window.focused(cx) == before_focus {\n                        break;\n                    }\n                }\n            }\n            return;\n        }\n\n        // Normal tab navigation\n        window.focus_prev(cx);\n    }\n}\n\nimpl Styled for Root {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Render for Root {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        window.set_rem_size(cx.theme().font_size);\n\n        window_border().shadow_size(self.window_shadow_size).child(\n            div()\n                .id(\"root\")\n                .key_context(CONTEXT)\n                .on_action(cx.listener(Self::on_action_tab))\n                .on_action(cx.listener(Self::on_action_tab_prev))\n                .relative()\n                .size_full()\n                .font_family(cx.theme().font_family.clone())\n                .bg(cx.theme().background)\n                .text_color(cx.theme().foreground)\n                .refine_style(&self.style)\n                .child(self.view.clone()),\n        )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/scroll/mod.rs",
    "content": "mod scrollable;\nmod scrollable_mask;\nmod scrollbar;\n\npub use scrollable::*;\npub use scrollable_mask::*;\npub use scrollbar::*;\n"
  },
  {
    "path": "crates/ui/src/scroll/scrollable.rs",
    "content": "use std::{panic::Location, rc::Rc};\n\nuse crate::{StyledExt, scroll::ScrollbarHandle};\n\nuse super::{Scrollbar, ScrollbarAxis};\nuse gpui::{\n    App, Div, Element, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce,\n    ScrollHandle, Stateful, StatefulInteractiveElement, StyleRefinement, Styled, Window, div,\n    prelude::FluentBuilder,\n};\n\n/// A trait for elements that can be made scrollable with scrollbars.\npub trait ScrollableElement: InteractiveElement + Styled + ParentElement + Element {\n    /// Adds a scrollbar to the element.\n    #[track_caller]\n    fn scrollbar<H: ScrollbarHandle + Clone>(\n        self,\n        scroll_handle: &H,\n        axis: impl Into<ScrollbarAxis>,\n    ) -> Self {\n        self.child(ScrollbarLayer {\n            id: \"scrollbar_layer\".into(),\n            axis: axis.into(),\n            scroll_handle: Rc::new(scroll_handle.clone()),\n        })\n    }\n\n    /// Adds a vertical scrollbar to the element.\n    #[track_caller]\n    fn vertical_scrollbar<H: ScrollbarHandle + Clone>(self, scroll_handle: &H) -> Self {\n        self.scrollbar(scroll_handle, ScrollbarAxis::Vertical)\n    }\n    /// Adds a horizontal scrollbar to the element.\n    #[track_caller]\n    fn horizontal_scrollbar<H: ScrollbarHandle + Clone>(self, scroll_handle: &H) -> Self {\n        self.scrollbar(scroll_handle, ScrollbarAxis::Horizontal)\n    }\n\n    /// Almost equivalent to [`StatefulInteractiveElement::overflow_scroll`], but adds scrollbars.\n    #[track_caller]\n    fn overflow_scrollbar(self) -> Scrollable<Self> {\n        Scrollable::new(self, ScrollbarAxis::Both)\n    }\n\n    /// Almost equivalent to [`StatefulInteractiveElement::overflow_x_scroll`], but adds Horizontal scrollbar.\n    #[track_caller]\n    fn overflow_x_scrollbar(self) -> Scrollable<Self> {\n        Scrollable::new(self, ScrollbarAxis::Horizontal)\n    }\n\n    /// Almost equivalent to [`StatefulInteractiveElement::overflow_y_scroll`], but adds Vertical scrollbar.\n    #[track_caller]\n    fn overflow_y_scrollbar(self) -> Scrollable<Self> {\n        Scrollable::new(self, ScrollbarAxis::Vertical)\n    }\n}\n\n/// A scrollable element wrapper that adds scrollbars to an interactive element.\n#[derive(IntoElement)]\npub struct Scrollable<E: InteractiveElement + Styled + ParentElement + Element> {\n    id: ElementId,\n    element: E,\n    axis: ScrollbarAxis,\n}\n\nimpl<E> Scrollable<E>\nwhere\n    E: InteractiveElement + Styled + ParentElement + Element,\n{\n    #[track_caller]\n    fn new(element: E, axis: impl Into<ScrollbarAxis>) -> Self {\n        let caller = Location::caller();\n        Self {\n            id: ElementId::CodeLocation(*caller),\n            element,\n            axis: axis.into(),\n        }\n    }\n}\n\nimpl<E> Styled for Scrollable<E>\nwhere\n    E: InteractiveElement + Styled + ParentElement + Element,\n{\n    fn style(&mut self) -> &mut StyleRefinement {\n        self.element.style()\n    }\n}\n\nimpl<E> ParentElement for Scrollable<E>\nwhere\n    E: InteractiveElement + Styled + ParentElement + Element,\n{\n    fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {\n        self.element.extend(elements)\n    }\n}\n\nimpl InteractiveElement for Scrollable<Div> {\n    fn interactivity(&mut self) -> &mut gpui::Interactivity {\n        self.element.interactivity()\n    }\n}\n\nimpl InteractiveElement for Scrollable<Stateful<Div>> {\n    fn interactivity(&mut self) -> &mut gpui::Interactivity {\n        self.element.interactivity()\n    }\n}\n\nimpl<E> RenderOnce for Scrollable<E>\nwhere\n    E: InteractiveElement + Styled + ParentElement + Element + 'static,\n{\n    fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let scroll_handle = window\n            .use_keyed_state(self.id.clone(), cx, |_, _| ScrollHandle::default())\n            .read(cx)\n            .clone();\n\n        // Inherit the size from the element style.\n        let style = StyleRefinement {\n            size: self.element.style().size.clone(),\n            ..Default::default()\n        };\n\n        div()\n            .id(self.id)\n            .size_full()\n            .refine_style(&style)\n            .relative()\n            .child(\n                div()\n                    .id(\"scroll-area\")\n                    .flex()\n                    .size_full()\n                    .track_scroll(&scroll_handle)\n                    .map(|this| match self.axis {\n                        ScrollbarAxis::Vertical => this.flex_col().overflow_y_scroll(),\n                        ScrollbarAxis::Horizontal => this.flex_row().overflow_x_scroll(),\n                        ScrollbarAxis::Both => this.overflow_scroll(),\n                    })\n                    .child(\n                        self.element\n                            // Refine element size to `flex_1`.\n                            .size_auto()\n                            .flex_1(),\n                    ),\n            )\n            .child(render_scrollbar(\n                \"scrollbar\",\n                &scroll_handle,\n                self.axis,\n                window,\n                cx,\n            ))\n    }\n}\n\nimpl ScrollableElement for Div {}\nimpl<E> ScrollableElement for Stateful<E>\nwhere\n    E: ParentElement + Styled + Element,\n    Self: InteractiveElement,\n{\n}\n\n#[derive(IntoElement)]\nstruct ScrollbarLayer<H: ScrollbarHandle + Clone> {\n    id: ElementId,\n    axis: ScrollbarAxis,\n    scroll_handle: Rc<H>,\n}\n\nimpl<H> RenderOnce for ScrollbarLayer<H>\nwhere\n    H: ScrollbarHandle + Clone + 'static,\n{\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        render_scrollbar(self.id, self.scroll_handle.as_ref(), self.axis, window, cx)\n    }\n}\n\n#[inline]\n#[track_caller]\nfn render_scrollbar<H: ScrollbarHandle + Clone>(\n    id: impl Into<ElementId>,\n    scroll_handle: &H,\n    axis: ScrollbarAxis,\n    window: &mut Window,\n    cx: &mut App,\n) -> Div {\n    // Do not render scrollbar when inspector is picking elements,\n    // to allow us to pick the background elements.\n    let is_inspector_picking = window.is_inspector_picking(cx);\n    if is_inspector_picking {\n        return div();\n    }\n\n    div()\n        .absolute()\n        .top_0()\n        .left_0()\n        .right_0()\n        .bottom_0()\n        .child(Scrollbar::new(scroll_handle).id(id).axis(axis))\n}\n"
  },
  {
    "path": "crates/ui/src/scroll/scrollable_mask.rs",
    "content": "use gpui::{\n    px, relative, App, Axis, BorderStyle, Bounds, ContentMask, Corners, Edges, Element, ElementId,\n    GlobalElementId, Hitbox, Hsla, IntoElement, IsZero as _, LayoutId, PaintQuad, Pixels, Point,\n    Position, ScrollHandle, ScrollWheelEvent, Style, Window,\n};\n\nuse crate::AxisExt;\n\n/// Make a scrollable mask element to cover the parent view with the mouse wheel event listening.\n///\n/// When the mouse wheel is scrolled, will move the `scroll_handle` scrolling with the `axis` direction.\n/// You can use this `scroll_handle` to control what you want to scroll.\n/// This is only can handle once axis scrolling.\npub struct ScrollableMask {\n    axis: Axis,\n    scroll_handle: ScrollHandle,\n    debug: Option<Hsla>,\n}\n\nimpl ScrollableMask {\n    /// Create a new scrollable mask element.\n    pub fn new(axis: Axis, scroll_handle: &ScrollHandle) -> Self {\n        Self {\n            scroll_handle: scroll_handle.clone(),\n            axis,\n            debug: None,\n        }\n    }\n\n    /// Enable the debug border, to show the mask bounds.\n    #[allow(dead_code)]\n    pub fn debug(mut self) -> Self {\n        self.debug = Some(gpui::yellow());\n        self\n    }\n}\n\nimpl IntoElement for ScrollableMask {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\nimpl Element for ScrollableMask {\n    type RequestLayoutState = ();\n    type PrepaintState = Hitbox;\n\n    fn id(&self) -> Option<ElementId> {\n        None\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        _: Option<&GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (LayoutId, Self::RequestLayoutState) {\n        let mut style = Style::default();\n        // Set the layout style relative to the table view to get same size.\n        style.position = Position::Absolute;\n        style.flex_grow = 1.0;\n        style.flex_shrink = 1.0;\n        style.size.width = relative(1.).into();\n        style.size.height = relative(1.).into();\n\n        (window.request_layout(style, None, cx), ())\n    }\n\n    fn prepaint(\n        &mut self,\n        _: Option<&GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        _: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        _: &mut App,\n    ) -> Self::PrepaintState {\n        // Move y to bounds height to cover the parent view.\n        let cover_bounds = Bounds {\n            origin: Point {\n                x: bounds.origin.x,\n                y: bounds.origin.y - bounds.size.height,\n            },\n            size: bounds.size,\n        };\n\n        window.insert_hitbox(cover_bounds, gpui::HitboxBehavior::Normal)\n    }\n\n    fn paint(\n        &mut self,\n        _: Option<&GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        _: Bounds<Pixels>,\n        _: &mut Self::RequestLayoutState,\n        hitbox: &mut Self::PrepaintState,\n        window: &mut Window,\n        _: &mut App,\n    ) {\n        let is_horizontal = self.axis.is_horizontal();\n        let line_height = window.line_height();\n        let bounds = hitbox.bounds;\n\n        window.with_content_mask(Some(ContentMask { bounds }), |window| {\n            if let Some(color) = self.debug {\n                window.paint_quad(PaintQuad {\n                    bounds,\n                    border_widths: Edges::all(px(1.0)),\n                    border_color: color,\n                    background: gpui::transparent_white().into(),\n                    corner_radii: Corners::all(px(0.)),\n                    border_style: BorderStyle::default(),\n                });\n            }\n\n            window.on_mouse_event({\n                let view_id = window.current_view();\n                let scroll_handle = self.scroll_handle.clone();\n\n                move |event: &ScrollWheelEvent, phase, _, cx| {\n                    if !(bounds.contains(&event.position) && phase.bubble()) {\n                        return;\n                    }\n\n                    let mut offset = scroll_handle.offset();\n                    let mut delta = event.delta.pixel_delta(line_height);\n\n                    // Limit for only one way scrolling at same time.\n                    // When use MacBook touchpad we may get both x and y delta,\n                    // only allows the one that more to scroll.\n                    if !delta.x.is_zero() && !delta.y.is_zero() {\n                        if delta.x.abs() > delta.y.abs() {\n                            delta.y = px(0.);\n                        } else {\n                            delta.x = px(0.);\n                        }\n                    }\n\n                    if is_horizontal {\n                        offset.x += delta.x;\n                    } else {\n                        offset.y += delta.y;\n                    }\n\n                    if offset != scroll_handle.offset() {\n                        scroll_handle.set_offset(offset);\n                        cx.notify(view_id);\n                        cx.stop_propagation();\n                    }\n                }\n            });\n        });\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/scroll/scrollbar.rs",
    "content": "use std::{\n    cell::Cell,\n    ops::Deref,\n    panic::Location,\n    rc::Rc,\n};\n\nuse instant::{Duration, Instant};\n\nuse crate::{ActiveTheme, AxisExt};\nuse gpui::{\n    App, Axis, BorderStyle, Bounds, ContentMask, Corner, CursorStyle, Edges, Element, ElementId,\n    GlobalElementId, Hitbox, HitboxBehavior, Hsla, InspectorElementId, IntoElement, IsZero,\n    LayoutId, ListState, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point,\n    Position, ScrollHandle, ScrollWheelEvent, Size, Style, UniformListScrollHandle, Window, fill,\n    point, px, relative, size,\n};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\n/// The width of the scrollbar (THUMB_ACTIVE_INSET * 2 + THUMB_ACTIVE_WIDTH)\nconst WIDTH: Pixels = px(4. * 2. + 8.);\nconst MIN_THUMB_SIZE: f32 = 48.;\n\nconst THUMB_WIDTH: Pixels = px(6.);\nconst THUMB_RADIUS: Pixels = px(6. / 2.);\nconst THUMB_INSET: Pixels = px(4.);\n\nconst THUMB_ACTIVE_WIDTH: Pixels = px(8.);\nconst THUMB_ACTIVE_RADIUS: Pixels = px(8. / 2.);\nconst THUMB_ACTIVE_INSET: Pixels = px(4.);\n\nconst FADE_OUT_DURATION: f32 = 3.0;\nconst FADE_OUT_DELAY: f32 = 2.0;\n\n/// Scrollbar show mode.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default, JsonSchema)]\npub enum ScrollbarShow {\n    /// Show scrollbar when scrolling, will fade out after idle.\n    #[default]\n    Scrolling,\n    /// Show scrollbar on hover.\n    Hover,\n    /// Always show scrollbar.\n    Always,\n}\n\nimpl ScrollbarShow {\n    fn is_hover(&self) -> bool {\n        matches!(self, Self::Hover)\n    }\n\n    fn is_always(&self) -> bool {\n        matches!(self, Self::Always)\n    }\n}\n\n/// A trait for scroll handles that can get and set offset.\npub trait ScrollbarHandle: 'static {\n    /// Get the current offset of the scroll handle.\n    fn offset(&self) -> Point<Pixels>;\n    /// Set the offset of the scroll handle.\n    fn set_offset(&self, offset: Point<Pixels>);\n    /// The full size of the content, including padding.\n    fn content_size(&self) -> Size<Pixels>;\n    /// Called when start dragging the scrollbar thumb.\n    fn start_drag(&self) {}\n    /// Called when end dragging the scrollbar thumb.\n    fn end_drag(&self) {}\n}\n\nimpl ScrollbarHandle for ScrollHandle {\n    fn offset(&self) -> Point<Pixels> {\n        self.offset()\n    }\n\n    fn set_offset(&self, offset: Point<Pixels>) {\n        self.set_offset(offset);\n    }\n\n    fn content_size(&self) -> Size<Pixels> {\n        (self.max_offset() + self.bounds().size.into()).into()\n    }\n}\n\nimpl ScrollbarHandle for UniformListScrollHandle {\n    fn offset(&self) -> Point<Pixels> {\n        self.0.borrow().base_handle.offset()\n    }\n\n    fn set_offset(&self, offset: Point<Pixels>) {\n        self.0.borrow_mut().base_handle.set_offset(offset)\n    }\n\n    fn content_size(&self) -> Size<Pixels> {\n        let base_handle = &self.0.borrow().base_handle;\n        (base_handle.max_offset() + base_handle.bounds().size.into()).into()\n    }\n}\n\nimpl ScrollbarHandle for ListState {\n    fn offset(&self) -> Point<Pixels> {\n        self.scroll_px_offset_for_scrollbar()\n    }\n\n    fn set_offset(&self, offset: Point<Pixels>) {\n        self.set_offset_from_scrollbar(offset);\n    }\n\n    fn content_size(&self) -> Size<Pixels> {\n        self.viewport_bounds().size + self.max_offset_for_scrollbar().into()\n    }\n\n    fn start_drag(&self) {\n        self.scrollbar_drag_started();\n    }\n\n    fn end_drag(&self) {\n        self.scrollbar_drag_ended();\n    }\n}\n\n#[doc(hidden)]\n#[derive(Debug, Clone)]\nstruct ScrollbarState(Rc<Cell<ScrollbarStateInner>>);\n\n#[doc(hidden)]\n#[derive(Debug, Clone, Copy)]\nstruct ScrollbarStateInner {\n    hovered_axis: Option<Axis>,\n    hovered_on_thumb: Option<Axis>,\n    dragged_axis: Option<Axis>,\n    drag_pos: Point<Pixels>,\n    last_scroll_offset: Point<Pixels>,\n    last_scroll_time: Option<Instant>,\n    // Last update offset\n    last_update: Instant,\n    idle_timer_scheduled: bool,\n}\n\nimpl Default for ScrollbarState {\n    fn default() -> Self {\n        Self(Rc::new(Cell::new(ScrollbarStateInner {\n            hovered_axis: None,\n            hovered_on_thumb: None,\n            dragged_axis: None,\n            drag_pos: point(px(0.), px(0.)),\n            last_scroll_offset: point(px(0.), px(0.)),\n            last_scroll_time: None,\n            last_update: Instant::now(),\n            idle_timer_scheduled: false,\n        })))\n    }\n}\n\nimpl Deref for ScrollbarState {\n    type Target = Rc<Cell<ScrollbarStateInner>>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl ScrollbarStateInner {\n    fn with_drag_pos(&self, axis: Axis, pos: Point<Pixels>) -> Self {\n        let mut state = *self;\n        if axis.is_vertical() {\n            state.drag_pos.y = pos.y;\n        } else {\n            state.drag_pos.x = pos.x;\n        }\n\n        state.dragged_axis = Some(axis);\n        state\n    }\n\n    fn with_unset_drag_pos(&self) -> Self {\n        let mut state = *self;\n        state.dragged_axis = None;\n        state\n    }\n\n    fn with_hovered(&self, axis: Option<Axis>) -> Self {\n        let mut state = *self;\n        state.hovered_axis = axis;\n        if axis.is_some() {\n            state.last_scroll_time = Some(Instant::now());\n        }\n        state\n    }\n\n    fn with_hovered_on_thumb(&self, axis: Option<Axis>) -> Self {\n        let mut state = *self;\n        state.hovered_on_thumb = axis;\n        if self.is_scrollbar_visible() {\n            if axis.is_some() {\n                state.last_scroll_time = Some(Instant::now());\n            }\n        }\n        state\n    }\n\n    fn with_last_scroll(\n        &self,\n        last_scroll_offset: Point<Pixels>,\n        last_scroll_time: Option<Instant>,\n    ) -> Self {\n        let mut state = *self;\n        state.last_scroll_offset = last_scroll_offset;\n        state.last_scroll_time = last_scroll_time;\n        state\n    }\n\n    fn with_last_scroll_time(&self, t: Option<Instant>) -> Self {\n        let mut state = *self;\n        state.last_scroll_time = t;\n        state\n    }\n\n    fn with_last_update(&self, t: Instant) -> Self {\n        let mut state = *self;\n        state.last_update = t;\n        state\n    }\n\n    fn with_idle_timer_scheduled(&self, scheduled: bool) -> Self {\n        let mut state = *self;\n        state.idle_timer_scheduled = scheduled;\n        state\n    }\n\n    fn is_scrollbar_visible(&self) -> bool {\n        // On drag\n        if self.dragged_axis.is_some() {\n            return true;\n        }\n\n        if let Some(last_time) = self.last_scroll_time {\n            let elapsed = Instant::now().duration_since(last_time).as_secs_f32();\n            elapsed < FADE_OUT_DURATION\n        } else {\n            false\n        }\n    }\n}\n\n/// Scrollbar axis.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum ScrollbarAxis {\n    /// Vertical scrollbar.\n    Vertical,\n    /// Horizontal scrollbar.\n    Horizontal,\n    /// Show both vertical and horizontal scrollbars.\n    Both,\n}\n\nimpl From<Axis> for ScrollbarAxis {\n    fn from(axis: Axis) -> Self {\n        match axis {\n            Axis::Vertical => Self::Vertical,\n            Axis::Horizontal => Self::Horizontal,\n        }\n    }\n}\n\nimpl ScrollbarAxis {\n    /// Return true if the scrollbar axis is vertical.\n    #[inline]\n    pub fn is_vertical(&self) -> bool {\n        matches!(self, Self::Vertical)\n    }\n\n    /// Return true if the scrollbar axis is horizontal.\n    #[inline]\n    pub fn is_horizontal(&self) -> bool {\n        matches!(self, Self::Horizontal)\n    }\n\n    /// Return true if the scrollbar axis is both vertical and horizontal.\n    #[inline]\n    pub fn is_both(&self) -> bool {\n        matches!(self, Self::Both)\n    }\n\n    /// Return true if the scrollbar has vertical axis.\n    #[inline]\n    pub fn has_vertical(&self) -> bool {\n        matches!(self, Self::Vertical | Self::Both)\n    }\n\n    /// Return true if the scrollbar has horizontal axis.\n    #[inline]\n    pub fn has_horizontal(&self) -> bool {\n        matches!(self, Self::Horizontal | Self::Both)\n    }\n\n    #[inline]\n    fn all(&self) -> Vec<Axis> {\n        match self {\n            Self::Vertical => vec![Axis::Vertical],\n            Self::Horizontal => vec![Axis::Horizontal],\n            // This should keep Horizontal first, Vertical is the primary axis\n            // if Vertical not need display, then Horizontal will not keep right margin.\n            Self::Both => vec![Axis::Horizontal, Axis::Vertical],\n        }\n    }\n}\n\n/// Scrollbar control for scroll-area or a uniform-list.\npub struct Scrollbar {\n    pub(crate) id: ElementId,\n    axis: ScrollbarAxis,\n    scrollbar_show: Option<ScrollbarShow>,\n    scroll_handle: Rc<dyn ScrollbarHandle>,\n    scroll_size: Option<Size<Pixels>>,\n    /// Maximum frames per second for scrolling by drag. Default is 120 FPS.\n    ///\n    /// This is used to limit the update rate of the scrollbar when it is\n    /// being dragged for some complex interactions for reducing CPU usage.\n    max_fps: usize,\n}\n\nimpl Scrollbar {\n    /// Create a new scrollbar.\n    ///\n    /// This will have both vertical and horizontal scrollbars.\n    #[track_caller]\n    pub fn new<H: ScrollbarHandle + Clone>(scroll_handle: &H) -> Self {\n        let caller = Location::caller();\n        Self {\n            id: ElementId::CodeLocation(*caller),\n            axis: ScrollbarAxis::Both,\n            scrollbar_show: None,\n            scroll_handle: Rc::new(scroll_handle.clone()),\n            max_fps: 120,\n            scroll_size: None,\n        }\n    }\n\n    /// Create with horizontal scrollbar.\n    #[track_caller]\n    pub fn horizontal<H: ScrollbarHandle + Clone>(scroll_handle: &H) -> Self {\n        Self::new(scroll_handle).axis(ScrollbarAxis::Horizontal)\n    }\n\n    /// Create with vertical scrollbar.\n    #[track_caller]\n    pub fn vertical<H: ScrollbarHandle + Clone>(scroll_handle: &H) -> Self {\n        Self::new(scroll_handle).axis(ScrollbarAxis::Vertical)\n    }\n\n    /// Set a specific element id, default is the [`Location::caller`].\n    ///\n    /// NOTE: In most cases, you don't need to set a specific id for scrollbar.\n    pub fn id(mut self, id: impl Into<ElementId>) -> Self {\n        self.id = id.into();\n        self\n    }\n\n    /// Set the scrollbar show mode [`ScrollbarShow`], if not set use the `cx.theme().scrollbar_show`.\n    pub fn scrollbar_show(mut self, scrollbar_show: ScrollbarShow) -> Self {\n        self.scrollbar_show = Some(scrollbar_show);\n        self\n    }\n\n    /// Set a special scroll size of the content area, default is None.\n    ///\n    /// Default will sync the `content_size` from `scroll_handle`.\n    pub fn scroll_size(mut self, scroll_size: Size<Pixels>) -> Self {\n        self.scroll_size = Some(scroll_size);\n        self\n    }\n\n    /// Set scrollbar axis.\n    pub fn axis(mut self, axis: impl Into<ScrollbarAxis>) -> Self {\n        self.axis = axis.into();\n        self\n    }\n\n    /// Set maximum frames per second for scrolling by drag. Default is 120 FPS.\n    ///\n    /// If you have very high CPU usage, consider reducing this value to improve performance.\n    ///\n    /// Available values: 30..120\n    pub(crate) fn max_fps(mut self, max_fps: usize) -> Self {\n        self.max_fps = max_fps.clamp(30, 120);\n        self\n    }\n\n    // Get the width of the scrollbar.\n    pub(crate) const fn width() -> Pixels {\n        WIDTH\n    }\n\n    fn style_for_active(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels, Pixels) {\n        (\n            cx.theme().scrollbar_thumb_hover,\n            cx.theme().scrollbar,\n            cx.theme().border,\n            THUMB_ACTIVE_WIDTH,\n            THUMB_ACTIVE_INSET,\n            THUMB_ACTIVE_RADIUS,\n        )\n    }\n\n    fn style_for_hovered_thumb(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels, Pixels) {\n        (\n            cx.theme().scrollbar_thumb_hover,\n            cx.theme().scrollbar,\n            cx.theme().border,\n            THUMB_ACTIVE_WIDTH,\n            THUMB_ACTIVE_INSET,\n            THUMB_ACTIVE_RADIUS,\n        )\n    }\n\n    fn style_for_hovered_bar(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels, Pixels) {\n        (\n            cx.theme().scrollbar_thumb,\n            cx.theme().scrollbar,\n            gpui::transparent_black(),\n            THUMB_ACTIVE_WIDTH,\n            THUMB_ACTIVE_INSET,\n            THUMB_ACTIVE_RADIUS,\n        )\n    }\n\n    fn style_for_normal(&self, cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels, Pixels) {\n        let scrollbar_show = self.scrollbar_show.unwrap_or(cx.theme().scrollbar_show);\n        let (width, inset, radius) = match scrollbar_show {\n            ScrollbarShow::Scrolling => (THUMB_WIDTH, THUMB_INSET, THUMB_RADIUS),\n            _ => (THUMB_ACTIVE_WIDTH, THUMB_ACTIVE_INSET, THUMB_ACTIVE_RADIUS),\n        };\n\n        (\n            cx.theme().scrollbar_thumb,\n            cx.theme().scrollbar,\n            gpui::transparent_black(),\n            width,\n            inset,\n            radius,\n        )\n    }\n\n    fn style_for_idle(&self, cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels, Pixels) {\n        let scrollbar_show = self.scrollbar_show.unwrap_or(cx.theme().scrollbar_show);\n        let (width, inset, radius) = match scrollbar_show {\n            ScrollbarShow::Scrolling => (THUMB_WIDTH, THUMB_INSET, THUMB_RADIUS),\n            _ => (THUMB_ACTIVE_WIDTH, THUMB_ACTIVE_INSET, THUMB_ACTIVE_RADIUS),\n        };\n\n        (\n            gpui::transparent_black(),\n            gpui::transparent_black(),\n            gpui::transparent_black(),\n            width,\n            inset,\n            radius,\n        )\n    }\n}\n\nimpl IntoElement for Scrollbar {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\n#[doc(hidden)]\npub struct PrepaintState {\n    hitbox: Hitbox,\n    scrollbar_state: ScrollbarState,\n    states: Vec<AxisPrepaintState>,\n}\n\n#[doc(hidden)]\npub struct AxisPrepaintState {\n    axis: Axis,\n    bar_hitbox: Hitbox,\n    bounds: Bounds<Pixels>,\n    radius: Pixels,\n    bg: Hsla,\n    border: Hsla,\n    thumb_bounds: Bounds<Pixels>,\n    // Bounds of thumb to be rendered.\n    thumb_fill_bounds: Bounds<Pixels>,\n    thumb_bg: Hsla,\n    scroll_size: Pixels,\n    container_size: Pixels,\n    thumb_size: Pixels,\n    margin_end: Pixels,\n}\n\nimpl Element for Scrollbar {\n    type RequestLayoutState = ();\n    type PrepaintState = PrepaintState;\n\n    fn id(&self) -> Option<gpui::ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        _: Option<&GlobalElementId>,\n        _: Option<&InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (LayoutId, Self::RequestLayoutState) {\n        let mut style = Style::default();\n        style.position = Position::Absolute;\n        style.flex_grow = 1.0;\n        style.flex_shrink = 1.0;\n        style.size.width = relative(1.).into();\n        style.size.height = relative(1.).into();\n\n        (window.request_layout(style, None, cx), ())\n    }\n\n    fn prepaint(\n        &mut self,\n        _: Option<&GlobalElementId>,\n        _: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        _: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::PrepaintState {\n        let hitbox = window.with_content_mask(Some(ContentMask { bounds }), |window| {\n            window.insert_hitbox(bounds, HitboxBehavior::Normal)\n        });\n\n        let state = window\n            .use_state(cx, |_, _| ScrollbarState::default())\n            .read(cx)\n            .clone();\n\n        let mut states = vec![];\n        let mut has_both = self.axis.is_both();\n        let scroll_size = self\n            .scroll_size\n            .unwrap_or(self.scroll_handle.content_size());\n\n        for axis in self.axis.all().into_iter() {\n            let is_vertical = axis.is_vertical();\n            let (scroll_area_size, container_size, scroll_position) = if is_vertical {\n                (\n                    scroll_size.height,\n                    hitbox.size.height,\n                    self.scroll_handle.offset().y,\n                )\n            } else {\n                (\n                    scroll_size.width,\n                    hitbox.size.width,\n                    self.scroll_handle.offset().x,\n                )\n            };\n\n            // The horizontal scrollbar is set avoid overlapping with the vertical scrollbar, if the vertical scrollbar is visible.\n            let margin_end = if has_both && !is_vertical {\n                WIDTH\n            } else {\n                px(0.)\n            };\n\n            // Hide scrollbar, if the scroll area is smaller than the container.\n            if scroll_area_size <= container_size {\n                has_both = false;\n                continue;\n            }\n\n            let thumb_length =\n                (container_size / scroll_area_size * container_size).max(px(MIN_THUMB_SIZE));\n            let thumb_start = -(scroll_position / (scroll_area_size - container_size)\n                * (container_size - margin_end - thumb_length));\n            let thumb_end = (thumb_start + thumb_length).min(container_size - margin_end);\n\n            let bounds = Bounds {\n                origin: if is_vertical {\n                    point(hitbox.origin.x + hitbox.size.width - WIDTH, hitbox.origin.y)\n                } else {\n                    point(\n                        hitbox.origin.x,\n                        hitbox.origin.y + hitbox.size.height - WIDTH,\n                    )\n                },\n                size: gpui::Size {\n                    width: if is_vertical {\n                        WIDTH\n                    } else {\n                        hitbox.size.width\n                    },\n                    height: if is_vertical {\n                        hitbox.size.height\n                    } else {\n                        WIDTH\n                    },\n                },\n            };\n\n            let scrollbar_show = self.scrollbar_show.unwrap_or(cx.theme().scrollbar_show);\n            let is_always_to_show = scrollbar_show.is_always();\n            let is_hover_to_show = scrollbar_show.is_hover();\n            let is_hovered_on_bar = state.get().hovered_axis == Some(axis);\n            let is_hovered_on_thumb = state.get().hovered_on_thumb == Some(axis);\n            let is_offset_changed = state.get().last_scroll_offset != self.scroll_handle.offset();\n\n            let (thumb_bg, bar_bg, bar_border, thumb_width, inset, radius) =\n                if state.get().dragged_axis == Some(axis) {\n                    Self::style_for_active(cx)\n                } else if is_hover_to_show && (is_hovered_on_bar || is_hovered_on_thumb) {\n                    if is_hovered_on_thumb {\n                        Self::style_for_hovered_thumb(cx)\n                    } else {\n                        Self::style_for_hovered_bar(cx)\n                    }\n                } else if is_offset_changed {\n                    self.style_for_normal(cx)\n                } else if is_always_to_show {\n                    if is_hovered_on_thumb {\n                        Self::style_for_hovered_thumb(cx)\n                    } else {\n                        Self::style_for_hovered_bar(cx)\n                    }\n                } else {\n                    let mut idle_state = self.style_for_idle(cx);\n                    // Delay 2s to fade out the scrollbar thumb (in 1s)\n                    if let Some(last_time) = state.get().last_scroll_time {\n                        let elapsed = Instant::now().duration_since(last_time).as_secs_f32();\n                        if is_hovered_on_bar {\n                            state.set(state.get().with_last_scroll_time(Some(Instant::now())));\n                            idle_state = if is_hovered_on_thumb {\n                                Self::style_for_hovered_thumb(cx)\n                            } else {\n                                Self::style_for_hovered_bar(cx)\n                            };\n                        } else if elapsed < FADE_OUT_DELAY {\n                            idle_state.0 = cx.theme().scrollbar_thumb;\n\n                            if !state.get().idle_timer_scheduled {\n                                let state = state.clone();\n                                state.set(state.get().with_idle_timer_scheduled(true));\n                                let current_view = window.current_view();\n                                let next_delay = Duration::from_secs_f32(FADE_OUT_DELAY - elapsed);\n                                window\n                                    .spawn(cx, async move |cx| {\n                                        cx.background_executor().timer(next_delay).await;\n                                        state.set(state.get().with_idle_timer_scheduled(false));\n                                        cx.update(|_, cx| cx.notify(current_view)).ok();\n                                    })\n                                    .detach();\n                            }\n                        } else if elapsed < FADE_OUT_DURATION {\n                            let opacity = 1.0 - (elapsed - FADE_OUT_DELAY).powi(10);\n                            idle_state.0 = cx.theme().scrollbar_thumb.opacity(opacity);\n\n                            window.request_animation_frame();\n                        }\n                    }\n\n                    idle_state\n                };\n\n            // The clickable area of the thumb\n            let thumb_length = thumb_end - thumb_start - inset * 2;\n            let thumb_bounds = if is_vertical {\n                Bounds::from_corner_and_size(\n                    Corner::TopRight,\n                    bounds.top_right() + point(-inset, inset + thumb_start),\n                    size(WIDTH, thumb_length),\n                )\n            } else {\n                Bounds::from_corner_and_size(\n                    Corner::BottomLeft,\n                    bounds.bottom_left() + point(inset + thumb_start, -inset),\n                    size(thumb_length, WIDTH),\n                )\n            };\n\n            // The actual render area of the thumb\n            let thumb_fill_bounds = if is_vertical {\n                Bounds::from_corner_and_size(\n                    Corner::TopRight,\n                    bounds.top_right() + point(-inset, inset + thumb_start),\n                    size(thumb_width, thumb_length),\n                )\n            } else {\n                Bounds::from_corner_and_size(\n                    Corner::BottomLeft,\n                    bounds.bottom_left() + point(inset + thumb_start, -inset),\n                    size(thumb_length, thumb_width),\n                )\n            };\n\n            let bar_hitbox = window.with_content_mask(Some(ContentMask { bounds }), |window| {\n                window.insert_hitbox(bounds, gpui::HitboxBehavior::Normal)\n            });\n\n            states.push(AxisPrepaintState {\n                axis,\n                bar_hitbox,\n                bounds,\n                radius,\n                bg: bar_bg,\n                border: bar_border,\n                thumb_bounds,\n                thumb_fill_bounds,\n                thumb_bg,\n                scroll_size: scroll_area_size,\n                container_size,\n                thumb_size: thumb_length,\n                margin_end,\n            })\n        }\n\n        PrepaintState {\n            hitbox,\n            states,\n            scrollbar_state: state,\n        }\n    }\n\n    fn paint(\n        &mut self,\n        _: Option<&GlobalElementId>,\n        _: Option<&InspectorElementId>,\n        _: Bounds<Pixels>,\n        _: &mut Self::RequestLayoutState,\n        prepaint: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        let scrollbar_state = &prepaint.scrollbar_state;\n        let scrollbar_show = self.scrollbar_show.unwrap_or(cx.theme().scrollbar_show);\n        let view_id = window.current_view();\n        let hitbox_bounds = prepaint.hitbox.bounds;\n        let is_visible = scrollbar_state.get().is_scrollbar_visible() || scrollbar_show.is_always();\n        let is_hover_to_show = scrollbar_show.is_hover();\n\n        // Update last_scroll_time when offset is changed.\n        if self.scroll_handle.offset() != scrollbar_state.get().last_scroll_offset {\n            scrollbar_state.set(\n                scrollbar_state\n                    .get()\n                    .with_last_scroll(self.scroll_handle.offset(), Some(Instant::now())),\n            );\n            cx.notify(view_id);\n        }\n\n        window.with_content_mask(\n            Some(ContentMask {\n                bounds: hitbox_bounds,\n            }),\n            |window| {\n                for state in prepaint.states.iter() {\n                    let axis = state.axis;\n                    let mut radius = state.radius;\n                    if cx.theme().radius.is_zero() {\n                        radius = px(0.);\n                    }\n                    let bounds = state.bounds;\n                    let thumb_bounds = state.thumb_bounds;\n                    let scroll_area_size = state.scroll_size;\n                    let container_size = state.container_size;\n                    let thumb_size = state.thumb_size;\n                    let margin_end = state.margin_end;\n                    let is_vertical = axis.is_vertical();\n\n                    window.set_cursor_style(CursorStyle::default(), &state.bar_hitbox);\n\n                    window.paint_layer(hitbox_bounds, |cx| {\n                        cx.paint_quad(fill(state.bounds, state.bg));\n\n                        cx.paint_quad(PaintQuad {\n                            bounds,\n                            corner_radii: (0.).into(),\n                            background: gpui::transparent_black().into(),\n                            border_widths: if is_vertical {\n                                Edges {\n                                    top: px(0.),\n                                    right: px(0.),\n                                    bottom: px(0.),\n                                    left: px(0.),\n                                }\n                            } else {\n                                Edges {\n                                    top: px(0.),\n                                    right: px(0.),\n                                    bottom: px(0.),\n                                    left: px(0.),\n                                }\n                            },\n                            border_color: state.border,\n                            border_style: BorderStyle::default(),\n                        });\n\n                        cx.paint_quad(\n                            fill(state.thumb_fill_bounds, state.thumb_bg).corner_radii(radius),\n                        );\n                    });\n\n                    window.on_mouse_event({\n                        let state = scrollbar_state.clone();\n                        let scroll_handle = self.scroll_handle.clone();\n\n                        move |event: &ScrollWheelEvent, phase, _, cx| {\n                            if phase.bubble() && hitbox_bounds.contains(&event.position) {\n                                if scroll_handle.offset() != state.get().last_scroll_offset {\n                                    state.set(state.get().with_last_scroll(\n                                        scroll_handle.offset(),\n                                        Some(Instant::now()),\n                                    ));\n                                    cx.notify(view_id);\n                                }\n                            }\n                        }\n                    });\n\n                    let safe_range = (-scroll_area_size + container_size)..px(0.);\n\n                    if is_hover_to_show || is_visible {\n                        window.on_mouse_event({\n                            let state = scrollbar_state.clone();\n                            let scroll_handle = self.scroll_handle.clone();\n\n                            move |event: &MouseDownEvent, phase, _, cx| {\n                                if phase.bubble() && bounds.contains(&event.position) {\n                                    cx.stop_propagation();\n\n                                    if thumb_bounds.contains(&event.position) {\n                                        // click on the thumb bar, set the drag position\n                                        let pos = event.position - thumb_bounds.origin;\n\n                                        scroll_handle.start_drag();\n                                        state.set(state.get().with_drag_pos(axis, pos));\n\n                                        cx.notify(view_id);\n                                    } else {\n                                        // click on the scrollbar, jump to the position\n                                        // Set the thumb bar center to the click position\n                                        let offset = scroll_handle.offset();\n                                        let percentage = if is_vertical {\n                                            (event.position.y - thumb_size / 2. - bounds.origin.y)\n                                                / (bounds.size.height - thumb_size)\n                                        } else {\n                                            (event.position.x - thumb_size / 2. - bounds.origin.x)\n                                                / (bounds.size.width - thumb_size)\n                                        }\n                                        .min(1.);\n\n                                        if is_vertical {\n                                            scroll_handle.set_offset(point(\n                                                offset.x,\n                                                (-scroll_area_size * percentage)\n                                                    .clamp(safe_range.start, safe_range.end),\n                                            ));\n                                        } else {\n                                            scroll_handle.set_offset(point(\n                                                (-scroll_area_size * percentage)\n                                                    .clamp(safe_range.start, safe_range.end),\n                                                offset.y,\n                                            ));\n                                        }\n                                    }\n                                }\n                            }\n                        });\n                    }\n\n                    window.on_mouse_event({\n                        let scroll_handle = self.scroll_handle.clone();\n                        let state = scrollbar_state.clone();\n                        let max_fps_duration = Duration::from_millis((1000 / self.max_fps) as u64);\n\n                        move |event: &MouseMoveEvent, _, _, cx| {\n                            let mut notify = false;\n                            // When is hover to show mode or it was visible,\n                            // we need to update the hovered state and increase the last_scroll_time.\n                            let need_hover_to_update = is_hover_to_show || is_visible;\n                            // Update hovered state for scrollbar\n                            if bounds.contains(&event.position) && need_hover_to_update {\n                                state.set(state.get().with_hovered(Some(axis)));\n\n                                if state.get().hovered_axis != Some(axis) {\n                                    notify = true;\n                                }\n                            } else {\n                                if state.get().hovered_axis == Some(axis) {\n                                    if state.get().hovered_axis.is_some() {\n                                        state.set(state.get().with_hovered(None));\n                                        notify = true;\n                                    }\n                                }\n                            }\n\n                            // Update hovered state for scrollbar thumb\n                            if thumb_bounds.contains(&event.position) {\n                                if state.get().hovered_on_thumb != Some(axis) {\n                                    state.set(state.get().with_hovered_on_thumb(Some(axis)));\n                                    notify = true;\n                                }\n                            } else {\n                                if state.get().hovered_on_thumb == Some(axis) {\n                                    state.set(state.get().with_hovered_on_thumb(None));\n                                    notify = true;\n                                }\n                            }\n\n                            // Move thumb position on dragging\n                            if state.get().dragged_axis == Some(axis) && event.dragging() {\n                                // Stop the event propagation to avoid selecting text or other side effects.\n                                cx.stop_propagation();\n\n                                // drag_pos is the position of the mouse down event\n                                // We need to keep the thumb bar still at the origin down position\n                                let drag_pos = state.get().drag_pos;\n\n                                let percentage = (if is_vertical {\n                                    (event.position.y - drag_pos.y - bounds.origin.y)\n                                        / (bounds.size.height - thumb_size)\n                                } else {\n                                    (event.position.x - drag_pos.x - bounds.origin.x)\n                                        / (bounds.size.width - thumb_size - margin_end)\n                                })\n                                .clamp(0., 1.);\n\n                                let offset = if is_vertical {\n                                    point(\n                                        scroll_handle.offset().x,\n                                        (-(scroll_area_size - container_size) * percentage)\n                                            .clamp(safe_range.start, safe_range.end),\n                                    )\n                                } else {\n                                    point(\n                                        (-(scroll_area_size - container_size) * percentage)\n                                            .clamp(safe_range.start, safe_range.end),\n                                        scroll_handle.offset().y,\n                                    )\n                                };\n\n                                if (scroll_handle.offset().y - offset.y).abs() > px(1.)\n                                    || (scroll_handle.offset().x - offset.x).abs() > px(1.)\n                                {\n                                    // Limit update rate\n                                    if state.get().last_update.elapsed() > max_fps_duration {\n                                        scroll_handle.set_offset(offset);\n                                        state.set(state.get().with_last_update(Instant::now()));\n                                        notify = true;\n                                    }\n                                }\n                            }\n\n                            if notify {\n                                cx.notify(view_id);\n                            }\n                        }\n                    });\n\n                    window.on_mouse_event({\n                        let state = scrollbar_state.clone();\n                        let scroll_handle = self.scroll_handle.clone();\n\n                        move |_event: &MouseUpEvent, phase, _, cx| {\n                            if phase.bubble() {\n                                scroll_handle.end_drag();\n                                state.set(state.get().with_unset_drag_pos());\n                                cx.notify(view_id);\n                            }\n                        }\n                    });\n                }\n            },\n        );\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/select.rs",
    "content": "use gpui::{\n    AnyElement, App, AppContext, Bounds, ClickEvent, Context, DismissEvent, Edges, ElementId,\n    Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding,\n    Length, ParentElement, Pixels, Render, RenderOnce, SharedString, StatefulInteractiveElement,\n    StyleRefinement, Styled, Subscription, Task, WeakEntity, Window, anchored, deferred, div,\n    prelude::FluentBuilder, px, rems,\n};\nuse rust_i18n::t;\n\nuse crate::{\n    ActiveTheme, Disableable, ElementExt as _, Icon, IconName, IndexPath, Selectable, Sizable,\n    Size, StyleSized, StyledExt,\n    actions::{Cancel, Confirm, SelectDown, SelectUp},\n    global_state::GlobalState,\n    h_flex,\n    input::{clear_button, input_style},\n    list::{List, ListDelegate, ListState},\n    v_flex,\n};\n\nconst CONTEXT: &str = \"Select\";\npub(crate) fn init(cx: &mut App) {\n    cx.bind_keys([\n        KeyBinding::new(\"up\", SelectUp, Some(CONTEXT)),\n        KeyBinding::new(\"down\", SelectDown, Some(CONTEXT)),\n        KeyBinding::new(\"enter\", Confirm { secondary: false }, Some(CONTEXT)),\n        KeyBinding::new(\n            \"secondary-enter\",\n            Confirm { secondary: true },\n            Some(CONTEXT),\n        ),\n        KeyBinding::new(\"escape\", Cancel, Some(CONTEXT)),\n    ])\n}\n\n/// A trait for items that can be displayed in a select.\npub trait SelectItem: Clone {\n    type Value: Clone;\n    fn title(&self) -> SharedString;\n    /// Customize the display title used to selected item in Select Input.\n    ///\n    /// If return None, the title will be used.\n    fn display_title(&self) -> Option<AnyElement> {\n        None\n    }\n    /// Render the item for the select dropdown menu, default is to render the title.\n    fn render(&self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        self.title().into_element()\n    }\n    /// Get the value of the item.\n    fn value(&self) -> &Self::Value;\n    /// Check if the item matches the query for search, default is to match the title.\n    fn matches(&self, query: &str) -> bool {\n        self.title().to_lowercase().contains(&query.to_lowercase())\n    }\n}\n\nimpl SelectItem for String {\n    type Value = Self;\n\n    fn title(&self) -> SharedString {\n        SharedString::from(self.to_string())\n    }\n\n    fn value(&self) -> &Self::Value {\n        &self\n    }\n}\n\nimpl SelectItem for SharedString {\n    type Value = Self;\n\n    fn title(&self) -> SharedString {\n        SharedString::from(self.to_string())\n    }\n\n    fn value(&self) -> &Self::Value {\n        &self\n    }\n}\n\nimpl SelectItem for &'static str {\n    type Value = Self;\n\n    fn title(&self) -> SharedString {\n        SharedString::from(self.to_string())\n    }\n\n    fn value(&self) -> &Self::Value {\n        self\n    }\n}\n\npub trait SelectDelegate: Sized {\n    type Item: SelectItem;\n\n    /// Returns the number of sections in the [`Select`].\n    fn sections_count(&self, _: &App) -> usize {\n        1\n    }\n\n    /// Returns the section header element for the given section index.\n    fn section(&self, _section: usize) -> Option<AnyElement> {\n        return None;\n    }\n\n    /// Returns the number of items in the given section.\n    fn items_count(&self, section: usize) -> usize;\n\n    /// Returns the item at the given index path (Only section, row will be use).\n    fn item(&self, ix: IndexPath) -> Option<&Self::Item>;\n\n    /// Returns the index of the item with the given value, or None if not found.\n    fn position<V>(&self, _value: &V) -> Option<IndexPath>\n    where\n        Self::Item: SelectItem<Value = V>,\n        V: PartialEq;\n\n    fn perform_search(\n        &mut self,\n        _query: &str,\n        _window: &mut Window,\n        _: &mut Context<SelectState<Self>>,\n    ) -> Task<()> {\n        Task::ready(())\n    }\n}\n\nimpl<T: SelectItem> SelectDelegate for Vec<T> {\n    type Item = T;\n\n    fn items_count(&self, _: usize) -> usize {\n        self.len()\n    }\n\n    fn item(&self, ix: IndexPath) -> Option<&Self::Item> {\n        self.as_slice().get(ix.row)\n    }\n\n    fn position<V>(&self, value: &V) -> Option<IndexPath>\n    where\n        Self::Item: SelectItem<Value = V>,\n        V: PartialEq,\n    {\n        self.iter()\n            .position(|v| v.value() == value)\n            .map(|ix| IndexPath::default().row(ix))\n    }\n}\n\nstruct SelectListDelegate<D: SelectDelegate + 'static> {\n    delegate: D,\n    state: WeakEntity<SelectState<D>>,\n    selected_index: Option<IndexPath>,\n}\n\nimpl<D> ListDelegate for SelectListDelegate<D>\nwhere\n    D: SelectDelegate + 'static,\n{\n    type Item = SelectListItem;\n\n    fn sections_count(&self, cx: &App) -> usize {\n        self.delegate.sections_count(cx)\n    }\n\n    fn items_count(&self, section: usize, _: &App) -> usize {\n        self.delegate.items_count(section)\n    }\n\n    fn render_section_header(\n        &mut self,\n        section: usize,\n        _: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> Option<impl IntoElement> {\n        let state = self.state.upgrade()?.read(cx);\n        let Some(item) = self.delegate.section(section) else {\n            return None;\n        };\n\n        return Some(\n            div()\n                .py_0p5()\n                .px_2()\n                .list_size(state.options.size)\n                .text_sm()\n                .text_color(cx.theme().muted_foreground)\n                .child(item),\n        );\n    }\n\n    fn render_item(\n        &mut self,\n        ix: IndexPath,\n        window: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> Option<Self::Item> {\n        let selected = self\n            .selected_index\n            .map_or(false, |selected_index| selected_index == ix);\n        let size = self\n            .state\n            .upgrade()\n            .map_or(Size::Medium, |state| state.read(cx).options.size);\n\n        if let Some(item) = self.delegate.item(ix) {\n            let list_item = SelectListItem::new(ix.row)\n                .selected(selected)\n                .with_size(size)\n                .child(div().whitespace_nowrap().child(item.render(window, cx)));\n            Some(list_item)\n        } else {\n            None\n        }\n    }\n\n    fn cancel(&mut self, window: &mut Window, cx: &mut Context<ListState<Self>>) {\n        let state = self.state.clone();\n        let final_selected_index = state\n            .read_with(cx, |this, _| this.final_selected_index)\n            .ok()\n            .flatten();\n\n        // If the selected index is not the final selected index, we need to restore it.\n        let need_restore = if final_selected_index != self.selected_index {\n            self.selected_index = final_selected_index;\n            true\n        } else {\n            false\n        };\n\n        cx.defer_in(window, move |this, window, cx| {\n            if need_restore {\n                this.set_selected_index(final_selected_index, window, cx);\n            }\n\n            _ = state.update(cx, |this, cx| {\n                this.set_open(false, cx);\n                this.focus(window, cx);\n            });\n        });\n    }\n\n    fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<ListState<Self>>) {\n        let selected_index = self.selected_index;\n        let selected_value = selected_index\n            .and_then(|ix| self.delegate.item(ix))\n            .map(|item| item.value().clone());\n        let state = self.state.clone();\n\n        cx.defer_in(window, move |_, window, cx| {\n            _ = state.update(cx, |this, cx| {\n                cx.emit(SelectEvent::Confirm(selected_value.clone()));\n                this.final_selected_index = selected_index;\n                this.selected_value = selected_value;\n                this.set_open(false, cx);\n                this.focus(window, cx);\n            });\n        });\n    }\n\n    fn perform_search(\n        &mut self,\n        query: &str,\n        window: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> Task<()> {\n        self.state.upgrade().map_or(Task::ready(()), |state| {\n            state.update(cx, |_, cx| self.delegate.perform_search(query, window, cx))\n        })\n    }\n\n    fn set_selected_index(\n        &mut self,\n        ix: Option<IndexPath>,\n        _: &mut Window,\n        _: &mut Context<ListState<Self>>,\n    ) {\n        self.selected_index = ix;\n    }\n\n    fn render_empty(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) -> impl IntoElement {\n        if let Some(empty) = self\n            .state\n            .upgrade()\n            .and_then(|state| state.read(cx).empty.as_ref())\n        {\n            empty(window, cx).into_any_element()\n        } else {\n            h_flex()\n                .justify_center()\n                .py_6()\n                .text_color(cx.theme().muted_foreground.opacity(0.6))\n                .child(Icon::new(IconName::Inbox).size(px(28.)))\n                .into_any_element()\n        }\n    }\n}\n\n/// Events emitted by the [`SelectState`].\npub enum SelectEvent<D: SelectDelegate + 'static> {\n    Confirm(Option<<D::Item as SelectItem>::Value>),\n}\n\nstruct SelectOptions {\n    style: StyleRefinement,\n    size: Size,\n    icon: Option<Icon>,\n    cleanable: bool,\n    placeholder: Option<SharedString>,\n    title_prefix: Option<SharedString>,\n    search_placeholder: Option<SharedString>,\n    empty: Option<AnyElement>,\n    menu_width: Length,\n    disabled: bool,\n    appearance: bool,\n}\n\nimpl Default for SelectOptions {\n    fn default() -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            size: Size::default(),\n            icon: None,\n            cleanable: false,\n            placeholder: None,\n            title_prefix: None,\n            empty: None,\n            menu_width: Length::Auto,\n            disabled: false,\n            appearance: true,\n            search_placeholder: None,\n        }\n    }\n}\n\n/// State of the [`Select`].\npub struct SelectState<D: SelectDelegate + 'static> {\n    focus_handle: FocusHandle,\n    options: SelectOptions,\n    searchable: bool,\n    list: Entity<ListState<SelectListDelegate<D>>>,\n    empty: Option<Box<dyn Fn(&Window, &App) -> AnyElement>>,\n    /// Store the bounds of the input\n    bounds: Bounds<Pixels>,\n    open: bool,\n    selected_value: Option<<D::Item as SelectItem>::Value>,\n    final_selected_index: Option<IndexPath>,\n    _subscriptions: Vec<Subscription>,\n}\n\n/// A Select element.\n#[derive(IntoElement)]\npub struct Select<D: SelectDelegate + 'static> {\n    id: ElementId,\n    state: Entity<SelectState<D>>,\n    options: SelectOptions,\n}\n\n/// A built-in searchable vector for select items.\n#[derive(Debug, Clone)]\npub struct SearchableVec<T> {\n    items: Vec<T>,\n    matched_items: Vec<T>,\n}\n\nimpl<T: Clone> SearchableVec<T> {\n    pub fn push(&mut self, item: T) {\n        self.items.push(item.clone());\n        self.matched_items.push(item);\n    }\n}\n\nimpl<T: Clone> SearchableVec<T> {\n    pub fn new(items: impl Into<Vec<T>>) -> Self {\n        let items = items.into();\n        Self {\n            items: items.clone(),\n            matched_items: items,\n        }\n    }\n}\n\nimpl<T: SelectItem> From<Vec<T>> for SearchableVec<T> {\n    fn from(items: Vec<T>) -> Self {\n        Self {\n            items: items.clone(),\n            matched_items: items,\n        }\n    }\n}\n\nimpl<I: SelectItem> SelectDelegate for SearchableVec<I> {\n    type Item = I;\n\n    fn items_count(&self, _: usize) -> usize {\n        self.matched_items.len()\n    }\n\n    fn item(&self, ix: IndexPath) -> Option<&Self::Item> {\n        self.matched_items.get(ix.row)\n    }\n\n    fn position<V>(&self, value: &V) -> Option<IndexPath>\n    where\n        Self::Item: SelectItem<Value = V>,\n        V: PartialEq,\n    {\n        for (ix, item) in self.matched_items.iter().enumerate() {\n            if item.value() == value {\n                return Some(IndexPath::default().row(ix));\n            }\n        }\n\n        None\n    }\n\n    fn perform_search(\n        &mut self,\n        query: &str,\n        _window: &mut Window,\n        _: &mut Context<SelectState<Self>>,\n    ) -> Task<()> {\n        self.matched_items = self\n            .items\n            .iter()\n            .filter(|item| item.matches(query))\n            .cloned()\n            .collect();\n\n        Task::ready(())\n    }\n}\n\nimpl<I: SelectItem> SelectDelegate for SearchableVec<SelectGroup<I>> {\n    type Item = I;\n\n    fn sections_count(&self, _: &App) -> usize {\n        self.matched_items.len()\n    }\n\n    fn items_count(&self, section: usize) -> usize {\n        self.matched_items\n            .get(section)\n            .map_or(0, |group| group.items.len())\n    }\n\n    fn section(&self, section: usize) -> Option<AnyElement> {\n        Some(\n            self.matched_items\n                .get(section)?\n                .title\n                .clone()\n                .into_any_element(),\n        )\n    }\n\n    fn item(&self, ix: IndexPath) -> Option<&Self::Item> {\n        let section = self.matched_items.get(ix.section)?;\n\n        section.items.get(ix.row)\n    }\n\n    fn position<V>(&self, value: &V) -> Option<IndexPath>\n    where\n        Self::Item: SelectItem<Value = V>,\n        V: PartialEq,\n    {\n        for (ix, group) in self.matched_items.iter().enumerate() {\n            for (row_ix, item) in group.items.iter().enumerate() {\n                if item.value() == value {\n                    return Some(IndexPath::default().section(ix).row(row_ix));\n                }\n            }\n        }\n\n        None\n    }\n\n    fn perform_search(\n        &mut self,\n        query: &str,\n        _window: &mut Window,\n        _: &mut Context<SelectState<Self>>,\n    ) -> Task<()> {\n        self.matched_items = self\n            .items\n            .iter()\n            .filter(|item| item.matches(&query))\n            .cloned()\n            .map(|mut item| {\n                item.items.retain(|item| item.matches(&query));\n                item\n            })\n            .collect();\n\n        Task::ready(())\n    }\n}\n\n/// A group of select items with a title.\n#[derive(Debug, Clone)]\npub struct SelectGroup<I: SelectItem> {\n    pub title: SharedString,\n    pub items: Vec<I>,\n}\n\nimpl<I> SelectGroup<I>\nwhere\n    I: SelectItem,\n{\n    /// Create a new SelectGroup with the given title.\n    pub fn new(title: impl Into<SharedString>) -> Self {\n        Self {\n            title: title.into(),\n            items: vec![],\n        }\n    }\n\n    /// Add an item to the group.\n    pub fn item(mut self, item: I) -> Self {\n        self.items.push(item);\n        self\n    }\n\n    /// Add multiple items to the group.\n    pub fn items(mut self, items: impl IntoIterator<Item = I>) -> Self {\n        self.items.extend(items);\n        self\n    }\n\n    fn matches(&self, query: &str) -> bool {\n        self.title.to_lowercase().contains(&query.to_lowercase())\n            || self.items.iter().any(|item| item.matches(query))\n    }\n}\n\nimpl<D> SelectState<D>\nwhere\n    D: SelectDelegate + 'static,\n{\n    /// Create a new Select state.\n    pub fn new(\n        delegate: D,\n        selected_index: Option<IndexPath>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Self {\n        let focus_handle = cx.focus_handle();\n        let delegate = SelectListDelegate {\n            delegate,\n            state: cx.entity().downgrade(),\n            selected_index,\n        };\n\n        let list = cx.new(|cx| ListState::new(delegate, window, cx).reset_on_cancel(false));\n        let list_focus_handle = list.read(cx).focus_handle.clone();\n        let list_search_focus_handle = list.read(cx).query_input.focus_handle(cx);\n\n        let _subscriptions = vec![\n            cx.on_blur(&list_focus_handle, window, Self::on_blur),\n            cx.on_blur(&list_search_focus_handle, window, Self::on_blur),\n            cx.on_blur(&focus_handle, window, Self::on_blur),\n        ];\n\n        let mut this = Self {\n            focus_handle,\n            options: SelectOptions::default(),\n            searchable: false,\n            list,\n            selected_value: None,\n            open: false,\n            bounds: Bounds::default(),\n            empty: None,\n            final_selected_index: None,\n            _subscriptions,\n        };\n        this.set_selected_index(selected_index, window, cx);\n        this\n    }\n\n    /// Sets whether the dropdown menu is searchable, default is `false`.\n    ///\n    /// When `true`, there will be a search input at the top of the dropdown menu.\n    pub fn searchable(mut self, searchable: bool) -> Self {\n        self.searchable = searchable;\n        self\n    }\n\n    /// Set the selected index for the select.\n    pub fn set_selected_index(\n        &mut self,\n        selected_index: Option<IndexPath>,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.list.update(cx, |list, cx| {\n            list._set_selected_index(selected_index, window, cx);\n        });\n        self.final_selected_index = selected_index;\n        self.update_selected_value(window, cx);\n    }\n\n    /// Set selected value for the select.\n    ///\n    /// This method will to get position from delegate and set selected index.\n    ///\n    /// If the value is not found, the None will be sets.\n    pub fn set_selected_value(\n        &mut self,\n        selected_value: &<D::Item as SelectItem>::Value,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) where\n        <<D as SelectDelegate>::Item as SelectItem>::Value: PartialEq,\n    {\n        let delegate = self.list.read(cx).delegate();\n        let selected_index = delegate.delegate.position(selected_value);\n        self.set_selected_index(selected_index, window, cx);\n    }\n\n    /// Set the items for the select state.\n    pub fn set_items(&mut self, items: D, _: &mut Window, cx: &mut Context<Self>)\n    where\n        D: SelectDelegate + 'static,\n    {\n        self.list.update(cx, |list, _| {\n            list.delegate_mut().delegate = items;\n        });\n    }\n\n    /// Get the selected index of the select.\n    pub fn selected_index(&self, cx: &App) -> Option<IndexPath> {\n        self.list.read(cx).selected_index()\n    }\n\n    /// Get the selected value of the select.\n    pub fn selected_value(&self) -> Option<&<D::Item as SelectItem>::Value> {\n        self.selected_value.as_ref()\n    }\n\n    /// Focus the select input.\n    pub fn focus(&self, window: &mut Window, cx: &mut App) {\n        self.focus_handle.focus(window, cx);\n    }\n\n    fn update_selected_value(&mut self, _: &Window, cx: &App) {\n        self.selected_value = self\n            .selected_index(cx)\n            .and_then(|ix| self.list.read(cx).delegate().delegate.item(ix))\n            .map(|item| item.value().clone());\n    }\n\n    fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        // When the select and dropdown menu are both not focused, close the dropdown menu.\n        if self.list.read(cx).is_focused(window, cx) || self.focus_handle.is_focused(window) {\n            return;\n        }\n\n        // If the selected index is not the final selected index, we need to restore it.\n        let final_selected_index = self.final_selected_index;\n        let selected_index = self.selected_index(cx);\n        if final_selected_index != selected_index {\n            self.list.update(cx, |list, cx| {\n                list.set_selected_index(self.final_selected_index, window, cx);\n            });\n        }\n\n        self.set_open(false, cx);\n        cx.notify();\n    }\n\n    fn up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {\n        if !self.open {\n            self.set_open(true, cx);\n        }\n\n        self.list.focus_handle(cx).focus(window, cx);\n        cx.propagate();\n    }\n\n    fn down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {\n        if !self.open {\n            self.set_open(true, cx);\n        }\n\n        self.list.focus_handle(cx).focus(window, cx);\n        cx.propagate();\n    }\n\n    fn enter(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {\n        // Propagate the event to the parent view, for example to the Dialog to support ENTER to confirm.\n        cx.propagate();\n\n        if !self.open {\n            self.set_open(true, cx);\n            cx.notify();\n        }\n\n        self.list.focus_handle(cx).focus(window, cx);\n    }\n\n    fn toggle_menu(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {\n        cx.stop_propagation();\n\n        self.set_open(!self.open, cx);\n        if self.open {\n            self.list.focus_handle(cx).focus(window, cx);\n        }\n        cx.notify();\n    }\n\n    fn escape(&mut self, _: &Cancel, _: &mut Window, cx: &mut Context<Self>) {\n        if !self.open {\n            cx.propagate();\n        }\n\n        self.set_open(false, cx);\n        cx.notify();\n    }\n\n    fn set_open(&mut self, open: bool, cx: &mut Context<Self>) {\n        self.open = open;\n        if self.open {\n            GlobalState::global_mut(cx).register_deferred_popover(&self.focus_handle)\n        } else {\n            GlobalState::global_mut(cx).unregister_deferred_popover(&self.focus_handle)\n        }\n        cx.notify();\n    }\n\n    fn clean(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {\n        cx.stop_propagation();\n        self.set_selected_index(None, window, cx);\n        cx.emit(SelectEvent::Confirm(None));\n    }\n\n    /// Returns the title element for the select input.\n    fn display_title(&mut self, _: &Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let default_title = div().text_color(cx.theme().muted_foreground).child(\n            self.options\n                .placeholder\n                .clone()\n                .unwrap_or_else(|| t!(\"Select.placeholder\").into()),\n        );\n\n        let Some(selected_index) = &self.selected_index(cx) else {\n            return default_title;\n        };\n\n        let Some(title) = self\n            .list\n            .read(cx)\n            .delegate()\n            .delegate\n            .item(*selected_index)\n            .map(|item| {\n                if let Some(el) = item.display_title() {\n                    el\n                } else {\n                    if let Some(prefix) = self.options.title_prefix.as_ref() {\n                        format!(\"{}{}\", prefix, item.title()).into_any_element()\n                    } else {\n                        item.title().into_any_element()\n                    }\n                }\n            })\n        else {\n            return default_title;\n        };\n\n        div()\n            .when(self.options.disabled, |this| {\n                this.text_color(cx.theme().muted_foreground)\n            })\n            .child(title)\n    }\n}\n\nimpl<D> Render for SelectState<D>\nwhere\n    D: SelectDelegate + 'static,\n{\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let searchable = self.searchable;\n        let is_focused = self.focus_handle.is_focused(window);\n        let show_clean = self.options.cleanable && self.selected_index(cx).is_some();\n        let bounds = self.bounds;\n        let allow_open = !(self.open || self.options.disabled);\n        let outline_visible = self.open || is_focused && !self.options.disabled;\n        let popup_radius = cx.theme().radius.min(px(8.));\n\n        let (bg, fg) = input_style(self.options.disabled, cx);\n\n        self.list\n            .update(cx, |list, cx| list.set_searchable(searchable, cx));\n\n        div()\n            .size_full()\n            .relative()\n            .child(\n                div()\n                    .id(\"input\")\n                    .relative()\n                    .flex()\n                    .items_center()\n                    .justify_between()\n                    .border_1()\n                    .border_color(cx.theme().transparent)\n                    .when(self.options.appearance, |this| {\n                        this.bg(bg)\n                            .text_color(fg)\n                            .when(self.options.disabled, |this| this.opacity(0.5))\n                            .border_color(cx.theme().input)\n                            .rounded(cx.theme().radius)\n                            .when(cx.theme().shadow, |this| this.shadow_xs())\n                    })\n                    .map(|this| {\n                        if self.options.disabled {\n                            this.shadow_none()\n                        } else {\n                            this\n                        }\n                    })\n                    .overflow_hidden()\n                    .input_size(self.options.size)\n                    .input_text_size(self.options.size)\n                    .refine_style(&self.options.style)\n                    .when(outline_visible, |this| this.focused_border(cx))\n                    .when(allow_open, |this| {\n                        this.on_click(cx.listener(Self::toggle_menu))\n                    })\n                    .child(\n                        h_flex()\n                            .id(\"inner\")\n                            .w_full()\n                            .items_center()\n                            .justify_between()\n                            .gap_1()\n                            .child(\n                                div()\n                                    .id(\"title\")\n                                    .w_full()\n                                    .overflow_hidden()\n                                    .whitespace_nowrap()\n                                    .truncate()\n                                    .child(self.display_title(window, cx)),\n                            )\n                            .when(show_clean, |this| {\n                                this.child(clear_button(cx).map(|this| {\n                                    if self.options.disabled {\n                                        this.disabled(true)\n                                    } else {\n                                        this.on_click(cx.listener(Self::clean))\n                                    }\n                                }))\n                            })\n                            .when(!show_clean, |this| {\n                                let icon = match self.options.icon.clone() {\n                                    Some(icon) => icon,\n                                    None => Icon::new(IconName::ChevronDown),\n                                };\n\n                                this.child(icon.xsmall().text_color(cx.theme().muted_foreground))\n                            }),\n                    )\n                    .on_prepaint({\n                        let state = cx.entity();\n                        move |bounds, _, cx| state.update(cx, |r, _| r.bounds = bounds)\n                    }),\n            )\n            .when(self.open, |this| {\n                this.child(\n                    deferred(\n                        anchored().snap_to_window_with_margin(px(8.)).child(\n                            div()\n                                .occlude()\n                                .map(|this| match self.options.menu_width {\n                                    Length::Auto => this.w(bounds.size.width + px(2.)),\n                                    Length::Definite(w) => this.w(w),\n                                })\n                                .child(\n                                    v_flex()\n                                        .occlude()\n                                        .mt_1p5()\n                                        .bg(cx.theme().background)\n                                        .border_1()\n                                        .border_color(cx.theme().border)\n                                        .rounded(popup_radius)\n                                        .shadow_md()\n                                        .child(\n                                            List::new(&self.list)\n                                                .when_some(\n                                                    self.options.search_placeholder.clone(),\n                                                    |this, placeholder| {\n                                                        this.search_placeholder(placeholder)\n                                                    },\n                                                )\n                                                .with_size(self.options.size)\n                                                .max_h(rems(20.))\n                                                .paddings(Edges::all(px(4.))),\n                                        ),\n                                )\n                                .on_mouse_down_out(cx.listener(|this, _, window, cx| {\n                                    this.escape(&Cancel, window, cx);\n                                })),\n                        ),\n                    )\n                    .with_priority(1),\n                )\n            })\n    }\n}\n\nimpl<D> Select<D>\nwhere\n    D: SelectDelegate + 'static,\n{\n    pub fn new(state: &Entity<SelectState<D>>) -> Self {\n        Self {\n            id: (\"select\", state.entity_id()).into(),\n            state: state.clone(),\n            options: SelectOptions::default(),\n        }\n    }\n\n    /// Set the width of the dropdown menu, default: Length::Auto\n    pub fn menu_width(mut self, width: impl Into<Length>) -> Self {\n        self.options.menu_width = width.into();\n        self\n    }\n\n    /// Set the placeholder for display when select value is empty.\n    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {\n        self.options.placeholder = Some(placeholder.into());\n        self\n    }\n\n    /// Set the right icon for the select input, instead of the default arrow icon.\n    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {\n        self.options.icon = Some(icon.into());\n        self\n    }\n\n    /// Set title prefix for the select.\n    ///\n    /// e.g.: Country: United States\n    ///\n    /// You should set the label is `Country: `\n    pub fn title_prefix(mut self, prefix: impl Into<SharedString>) -> Self {\n        self.options.title_prefix = Some(prefix.into());\n        self\n    }\n\n    /// Set whether to show the clear button when the input field is not empty, default is false.\n    pub fn cleanable(mut self, cleanable: bool) -> Self {\n        self.options.cleanable = cleanable;\n        self\n    }\n\n    /// Sets the placeholder text for the search input.\n    pub fn search_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {\n        self.options.search_placeholder = Some(placeholder.into());\n        self\n    }\n\n    /// Set the disable state for the select.\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.options.disabled = disabled;\n        self\n    }\n\n    /// Set the element to display when the select list is empty.\n    pub fn empty(mut self, el: impl IntoElement) -> Self {\n        self.options.empty = Some(el.into_any_element());\n        self\n    }\n\n    /// Set the appearance of the select, if false the select input will no border, background.\n    pub fn appearance(mut self, appearance: bool) -> Self {\n        self.options.appearance = appearance;\n        self\n    }\n}\n\nimpl<D> Sizable for Select<D>\nwhere\n    D: SelectDelegate + 'static,\n{\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.options.size = size.into();\n        self\n    }\n}\n\nimpl<D> EventEmitter<SelectEvent<D>> for SelectState<D> where D: SelectDelegate + 'static {}\nimpl<D> EventEmitter<DismissEvent> for SelectState<D> where D: SelectDelegate + 'static {}\nimpl<D> Focusable for SelectState<D>\nwhere\n    D: SelectDelegate,\n{\n    fn focus_handle(&self, cx: &App) -> FocusHandle {\n        if self.open {\n            self.list.focus_handle(cx)\n        } else {\n            self.focus_handle.clone()\n        }\n    }\n}\n\nimpl<D> Styled for Select<D>\nwhere\n    D: SelectDelegate,\n{\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.options.style\n    }\n}\n\nimpl<D> RenderOnce for Select<D>\nwhere\n    D: SelectDelegate + 'static,\n{\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let disabled = self.options.disabled;\n        let focus_handle = self.state.focus_handle(cx);\n        // If the size has change, set size to self.list, to change the QueryInput size.\n        self.state.update(cx, |this, _| {\n            this.options = self.options;\n        });\n\n        div()\n            .id(self.id.clone())\n            .key_context(CONTEXT)\n            .when(!disabled, |this| {\n                this.track_focus(&focus_handle.tab_stop(true))\n            })\n            .on_action(window.listener_for(&self.state, SelectState::up))\n            .on_action(window.listener_for(&self.state, SelectState::down))\n            .on_action(window.listener_for(&self.state, SelectState::enter))\n            .on_action(window.listener_for(&self.state, SelectState::escape))\n            .size_full()\n            .child(self.state)\n    }\n}\n\n#[derive(IntoElement)]\nstruct SelectListItem {\n    id: ElementId,\n    size: Size,\n    style: StyleRefinement,\n    selected: bool,\n    disabled: bool,\n    children: Vec<AnyElement>,\n}\n\nimpl SelectListItem {\n    pub fn new(ix: usize) -> Self {\n        Self {\n            id: (\"select-item\", ix).into(),\n            size: Size::default(),\n            style: StyleRefinement::default(),\n            selected: false,\n            disabled: false,\n            children: Vec::new(),\n        }\n    }\n}\n\nimpl ParentElement for SelectListItem {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Disableable for SelectListItem {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl Selectable for SelectListItem {\n    fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        self.selected\n    }\n}\n\nimpl Sizable for SelectListItem {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Styled for SelectListItem {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for SelectListItem {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        h_flex()\n            .id(self.id)\n            .relative()\n            .gap_x_1()\n            .py_1()\n            .px_2()\n            .rounded(cx.theme().radius)\n            .text_base()\n            .text_color(cx.theme().foreground)\n            .relative()\n            .items_center()\n            .justify_between()\n            .input_text_size(self.size)\n            .list_size(self.size)\n            .refine_style(&self.style)\n            .when(!self.disabled, |this| {\n                this.when(!self.selected, |this| {\n                    this.hover(|this| this.bg(cx.theme().accent.alpha(0.7)))\n                })\n            })\n            .when(self.selected, |this| this.bg(cx.theme().accent))\n            .when(self.disabled, |this| {\n                this.text_color(cx.theme().muted_foreground)\n            })\n            .child(\n                h_flex()\n                    .w_full()\n                    .items_center()\n                    .justify_between()\n                    .gap_x_1()\n                    .child(div().w_full().children(self.children)),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/setting/fields/bool.rs",
    "content": "use std::rc::Rc;\n\nuse crate::{\n    checkbox::Checkbox,\n    setting::{\n        fields::{get_value, set_value, SettingFieldRender},\n        AnySettingField, RenderOptions,\n    },\n    switch::Switch,\n    Sizable, StyledExt,\n};\nuse gpui::{div, AnyElement, App, IntoElement, ParentElement as _, StyleRefinement, Window};\n\npub(crate) struct BoolField {\n    use_switch: bool,\n}\n\nimpl BoolField {\n    pub(crate) fn new(use_switch: bool) -> Self {\n        Self { use_switch }\n    }\n}\n\nimpl SettingFieldRender for BoolField {\n    fn render(\n        &self,\n        field: Rc<dyn AnySettingField>,\n        options: &RenderOptions,\n        style: &StyleRefinement,\n        _: &mut Window,\n        cx: &mut App,\n    ) -> AnyElement {\n        let checked = get_value::<bool>(&field, cx);\n        let set_value = set_value::<bool>(&field, cx);\n\n        div()\n            .refine_style(style)\n            .child(if self.use_switch {\n                Switch::new(\"check\")\n                    .checked(checked)\n                    .with_size(options.size)\n                    .on_click(move |checked: &bool, _, cx: &mut App| {\n                        set_value(*checked, cx);\n                    })\n                    .into_any_element()\n            } else {\n                Checkbox::new(\"check\")\n                    .checked(checked)\n                    .with_size(options.size)\n                    .on_click(move |checked: &bool, _, cx: &mut App| {\n                        set_value(*checked, cx);\n                    })\n                    .into_any_element()\n            })\n            .into_any_element()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/setting/fields/dropdown.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{\n    AnyElement, App, Corner, IntoElement, SharedString, StyleRefinement, Styled, Window,\n    prelude::FluentBuilder as _,\n};\n\nuse crate::{\n    AxisExt, Sizable, StyledExt,\n    button::Button,\n    menu::{DropdownMenu, PopupMenuItem},\n    setting::{\n        AnySettingField, RenderOptions,\n        fields::{SettingFieldRender, get_value, set_value},\n    },\n};\n\npub(crate) struct DropdownField<T> {\n    options: Vec<(SharedString, SharedString)>,\n    _marker: std::marker::PhantomData<T>,\n}\n\nimpl<T> DropdownField<T> {\n    pub(crate) fn new(options: Option<&Vec<(SharedString, SharedString)>>) -> Self {\n        Self {\n            options: options.cloned().unwrap_or(vec![]),\n            _marker: std::marker::PhantomData,\n        }\n    }\n}\n\nimpl<T> SettingFieldRender for DropdownField<T>\nwhere\n    T: Into<SharedString> + From<SharedString> + Clone + 'static,\n{\n    fn render(\n        &self,\n        field: Rc<dyn AnySettingField>,\n        options: &RenderOptions,\n        style: &StyleRefinement,\n        _: &mut Window,\n        cx: &mut App,\n    ) -> AnyElement {\n        let old_value = get_value::<T>(&field, cx);\n        let set_value = set_value::<T>(&field, cx);\n        let dropdown_options = self.options.clone();\n\n        let old_label = dropdown_options\n            .iter()\n            .find(|(value, _)| *value == old_value.clone().into())\n            .map(|(_, label)| label.clone())\n            .unwrap_or_else(|| old_value.clone().into());\n\n        Button::new(\"btn\")\n            .when(options.layout.is_vertical(), |this| this.w_full())\n            .label(old_label)\n            .dropdown_caret(true)\n            .outline()\n            .with_size(options.size)\n            .refine_style(style)\n            .dropdown_menu_with_anchor(Corner::TopRight, move |menu, _, _| {\n                let set_value = set_value.clone();\n                let menu = dropdown_options.iter().fold(menu, |menu, (value, label)| {\n                    let old_value: SharedString = old_value.clone().into();\n                    let checked = &old_value == value;\n                    menu.item(\n                        PopupMenuItem::new(label.clone())\n                            .checked(checked)\n                            .on_click({\n                                let value = value.clone();\n                                let set_value = set_value.clone();\n                                move |_, _, cx| {\n                                    set_value(T::from(value.clone()), cx);\n                                }\n                            }),\n                    )\n                });\n                menu\n            })\n            .into_any_element()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/setting/fields/element.rs",
    "content": "use gpui::{AnyElement, App, IntoElement, StyleRefinement, Window};\nuse std::rc::Rc;\n\nuse crate::setting::{fields::SettingFieldRender, AnySettingField, RenderOptions};\n\n/// A trait for rendering custom setting field elements.\n///\n/// For [`crate::setting::SettingField::element`] method.\npub trait SettingFieldElement {\n    type Element: IntoElement + 'static;\n\n    fn render_field(\n        &self,\n        options: &RenderOptions,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::Element;\n}\n\nimpl<F, E> SettingFieldElement for F\nwhere\n    E: IntoElement + 'static,\n    F: Fn(&RenderOptions, &mut Window, &mut App) -> E,\n{\n    type Element = E;\n\n    fn render_field(\n        &self,\n        options: &RenderOptions,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::Element {\n        (self)(options, window, cx)\n    }\n}\n\npub(crate) struct AnySettingFieldElement<T>(pub(crate) T);\nimpl<T> SettingFieldElement for AnySettingFieldElement<T>\nwhere\n    T: SettingFieldElement,\n{\n    type Element = AnyElement;\n\n    fn render_field(\n        &self,\n        options: &RenderOptions,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::Element {\n        self.0.render_field(options, window, cx).into_any_element()\n    }\n}\nimpl SettingFieldElement for Rc<dyn SettingFieldElement<Element = AnyElement>> {\n    type Element = AnyElement;\n\n    fn render_field(\n        &self,\n        options: &RenderOptions,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::Element {\n        self.as_ref().render_field(options, window, cx)\n    }\n}\n\npub(crate) struct ElementField {\n    element_render: Rc<dyn SettingFieldElement<Element = AnyElement>>,\n}\n\nimpl ElementField {\n    pub(crate) fn new<E>(element_render: E) -> Self\n    where\n        E: SettingFieldElement<Element = AnyElement> + 'static,\n    {\n        Self {\n            element_render: Rc::new(element_render),\n        }\n    }\n}\n\nimpl SettingFieldRender for ElementField {\n    fn render(\n        &self,\n        _: Rc<dyn AnySettingField>,\n        options: &RenderOptions,\n        _style: &StyleRefinement,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> AnyElement {\n        (self.element_render).render_field(options, window, cx)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/setting/fields/mod.rs",
    "content": "mod bool;\nmod dropdown;\nmod element;\nmod number;\nmod string;\n\npub(crate) use bool::*;\npub(crate) use dropdown::*;\npub(crate) use element::*;\npub(crate) use number::*;\npub(crate) use string::*;\n\npub use element::SettingFieldElement;\npub use number::NumberFieldOptions;\n\nuse gpui::{AnyElement, App, IntoElement, SharedString, StyleRefinement, Styled, Window};\nuse std::{any::Any, rc::Rc};\n\nuse crate::setting::RenderOptions;\n\npub(crate) trait SettingFieldRender {\n    #[allow(clippy::too_many_arguments)]\n    fn render(\n        &self,\n        field: Rc<dyn AnySettingField>,\n        options: &RenderOptions,\n        style: &StyleRefinement,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> AnyElement;\n}\n\npub(crate) fn get_value<T: Clone + 'static>(field: &Rc<dyn AnySettingField>, cx: &mut App) -> T {\n    let setting_field = field\n        .as_any()\n        .downcast_ref::<SettingField<T>>()\n        .expect(\"Failed to downcast setting field\");\n    (setting_field.value)(cx)\n}\n\npub(crate) fn set_value<T: Clone + 'static>(\n    field: &Rc<dyn AnySettingField>,\n    _cx: &mut App,\n) -> Rc<dyn Fn(T, &mut App)> {\n    let setting_field = field\n        .as_any()\n        .downcast_ref::<SettingField<T>>()\n        .expect(\"Failed to downcast setting field\");\n    setting_field.set_value.clone()\n}\n\n/// The type of setting field to render.\n#[derive(Clone)]\npub enum SettingFieldType {\n    Switch,\n    Checkbox,\n    NumberInput {\n        options: NumberFieldOptions,\n    },\n    Input,\n    Dropdown {\n        options: Vec<(SharedString, SharedString)>,\n    },\n    Element {\n        element: Rc<dyn SettingFieldElement<Element = AnyElement>>,\n    },\n}\n\nimpl SettingFieldType {\n    #[inline]\n    pub(crate) fn is_switch(&self) -> bool {\n        matches!(self, SettingFieldType::Switch)\n    }\n\n    #[inline]\n    pub(crate) fn is_number_input(&self) -> bool {\n        matches!(self, SettingFieldType::NumberInput { .. })\n    }\n\n    #[inline]\n    pub(crate) fn is_input(&self) -> bool {\n        matches!(self, SettingFieldType::Input)\n    }\n\n    #[inline]\n    pub(crate) fn is_dropdown(&self) -> bool {\n        matches!(self, SettingFieldType::Dropdown { .. })\n    }\n\n    #[inline]\n    pub(crate) fn is_element(&self) -> bool {\n        matches!(self, SettingFieldType::Element { .. })\n    }\n\n    #[inline]\n    pub(super) fn dropdown_options(&self) -> Option<&Vec<(SharedString, SharedString)>> {\n        match self {\n            SettingFieldType::Dropdown { options } => Some(options),\n            _ => None,\n        }\n    }\n\n    #[inline]\n    pub(super) fn number_input_options(&self) -> Option<&NumberFieldOptions> {\n        match self {\n            SettingFieldType::NumberInput { options } => Some(options),\n            _ => None,\n        }\n    }\n\n    #[inline]\n    pub(super) fn element(&self) -> Rc<dyn SettingFieldElement<Element = AnyElement>> {\n        match self {\n            SettingFieldType::Element { element } => element.clone(),\n            _ => unreachable!(\"element_render called on non-element field\"),\n        }\n    }\n}\n\n/// A setting field that can get and set a value of type T in the App.\npub struct SettingField<T> {\n    pub(crate) field_type: SettingFieldType,\n    pub(crate) style: StyleRefinement,\n    /// Function to get the value for this field.\n    pub(crate) value: Rc<dyn Fn(&App) -> T>,\n    /// Function to set the value for this field.\n    pub(crate) set_value: Rc<dyn Fn(T, &mut App)>,\n    pub(crate) default_value: Option<T>,\n}\n\nimpl SettingField<bool> {\n    /// Create a new Switch field.\n    pub fn switch<V, S>(value: V, set_value: S) -> Self\n    where\n        V: Fn(&App) -> bool + 'static,\n        S: Fn(bool, &mut App) + 'static,\n    {\n        Self::new(SettingFieldType::Switch, value, set_value)\n    }\n\n    /// Create a new Checkbox field.\n    pub fn checkbox<V, S>(value: V, set_value: S) -> Self\n    where\n        V: Fn(&App) -> bool + 'static,\n        S: Fn(bool, &mut App) + 'static,\n    {\n        Self::new(SettingFieldType::Checkbox, value, set_value)\n    }\n}\n\nimpl SettingField<SharedString> {\n    /// Create a new Input field.\n    pub fn input<V, S>(value: V, set_value: S) -> Self\n    where\n        V: Fn(&App) -> SharedString + 'static,\n        S: Fn(SharedString, &mut App) + 'static,\n    {\n        Self::new(SettingFieldType::Input, value, set_value)\n    }\n\n    /// Create a new Dropdown field with the given options.\n    pub fn dropdown<V, S>(\n        options: Vec<(SharedString, SharedString)>,\n        value: V,\n        set_value: S,\n    ) -> Self\n    where\n        V: Fn(&App) -> SharedString + 'static,\n        S: Fn(SharedString, &mut App) + 'static,\n    {\n        Self::new(SettingFieldType::Dropdown { options }, value, set_value)\n    }\n\n    /// Create a new setting field with the given custom element that implements [`SettingFieldElement`] trait.\n    ///\n    /// See also [`SettingField::render`] for simply building with a render closure.\n    pub fn element<E>(element: E) -> Self\n    where\n        E: SettingFieldElement + 'static,\n    {\n        Self::new(\n            SettingFieldType::Element {\n                element: Rc::new(AnySettingFieldElement(element)),\n            },\n            |_| SharedString::default(),\n            |_, _| {},\n        )\n    }\n\n    /// Create a new setting field with the given element render closure.\n    ///\n    /// See also [`SettingField::element`] for building with a custom field for more complex scenarios.\n    pub fn render<E, R>(element_render: R) -> Self\n    where\n        E: IntoElement + 'static,\n        R: Fn(&RenderOptions, &mut Window, &mut App) -> E + 'static,\n    {\n        Self::element(\n            move |options: &RenderOptions, window: &mut Window, cx: &mut App| {\n                (element_render)(options, window, cx).into_any_element()\n            },\n        )\n    }\n}\n\nimpl SettingField<f64> {\n    /// Create a new Number Input field with the given options.\n    pub fn number_input<V, S>(options: NumberFieldOptions, value: V, set_value: S) -> Self\n    where\n        V: Fn(&App) -> f64 + 'static,\n        S: Fn(f64, &mut App) + 'static,\n    {\n        Self::new(SettingFieldType::NumberInput { options }, value, set_value)\n    }\n}\n\nimpl<T> SettingField<T> {\n    /// Create a new setting field with the given get and set functions.\n    fn new<V, S>(field_type: SettingFieldType, value: V, set_value: S) -> Self\n    where\n        V: Fn(&App) -> T + 'static,\n        S: Fn(T, &mut App) + 'static,\n    {\n        Self {\n            field_type,\n            style: StyleRefinement::default(),\n            value: Rc::new(value),\n            set_value: Rc::new(set_value),\n            default_value: None,\n        }\n    }\n\n    /// Set the default value for this setting field, default is None.\n    ///\n    /// If set, this value can be used to reset the setting to its default state.\n    /// If not set, the setting cannot be reset.\n    pub fn default_value(mut self, default_value: impl Into<T>) -> Self {\n        self.default_value = Some(default_value.into());\n        self\n    }\n}\n\nimpl<T> Styled for SettingField<T> {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\n/// A trait for setting fields that allows for dynamic typing.\npub trait AnySettingField {\n    fn as_any(&self) -> &dyn std::any::Any;\n    fn type_name(&self) -> &'static str;\n    fn type_id(&self) -> std::any::TypeId;\n    fn field_type(&self) -> &SettingFieldType;\n    fn style(&self) -> &StyleRefinement;\n    fn is_resettable(&self, cx: &App) -> bool;\n    fn reset(&self, window: &mut Window, cx: &mut App);\n}\n\nimpl<T: Clone + PartialEq + Send + Sync + 'static> AnySettingField for SettingField<T> {\n    fn as_any(&self) -> &dyn Any {\n        self\n    }\n\n    fn type_name(&self) -> &'static str {\n        std::any::type_name::<T>()\n    }\n\n    fn type_id(&self) -> std::any::TypeId {\n        std::any::TypeId::of::<T>()\n    }\n\n    fn field_type(&self) -> &SettingFieldType {\n        &self.field_type\n    }\n\n    fn style(&self) -> &StyleRefinement {\n        &self.style\n    }\n\n    fn is_resettable(&self, cx: &App) -> bool {\n        let Some(default_value) = self.default_value.as_ref() else {\n            return false;\n        };\n\n        &(self.value)(cx) != default_value\n    }\n\n    fn reset(&self, _: &mut Window, cx: &mut App) {\n        let Some(default_value) = self.default_value.as_ref() else {\n            return;\n        };\n\n        (self.set_value)(default_value.clone(), cx)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/setting/fields/number.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{\n    AnyElement, App, AppContext as _, Entity, IntoElement, SharedString, StyleRefinement, Styled,\n    Subscription, Window, prelude::FluentBuilder as _,\n};\n\nuse crate::{\n    AxisExt, Sizable, StyledExt,\n    input::{InputEvent, InputState, NumberInput, NumberInputEvent, StepAction},\n    setting::{\n        AnySettingField, RenderOptions,\n        fields::{SettingFieldRender, get_value, set_value},\n    },\n};\n\n#[derive(Clone, Debug)]\npub struct NumberFieldOptions {\n    /// The minimum value for the number input, default is `f64::MIN`.\n    pub min: f64,\n    /// The maximum value for the number input, default is `f64::MAX`.\n    pub max: f64,\n    /// The step value for the number input, default is `1.0`.\n    pub step: f64,\n}\n\nimpl Default for NumberFieldOptions {\n    fn default() -> Self {\n        Self {\n            min: f64::MIN,\n            max: f64::MAX,\n            step: 1.0,\n        }\n    }\n}\n\npub(crate) struct NumberField {\n    options: NumberFieldOptions,\n}\n\nimpl NumberField {\n    pub(crate) fn new(options: Option<&NumberFieldOptions>) -> Self {\n        Self {\n            options: options.cloned().unwrap_or_default(),\n        }\n    }\n}\n\nstruct State {\n    input: Entity<InputState>,\n    initial_value: f64,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl SettingFieldRender for NumberField {\n    fn render(\n        &self,\n        field: Rc<dyn AnySettingField>,\n        options: &RenderOptions,\n        style: &StyleRefinement,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> AnyElement {\n        let value = get_value::<f64>(&field, cx);\n        let set_value = set_value::<f64>(&field, cx);\n        let num_options = self.options.clone();\n\n        let state = window\n            .use_keyed_state(\n                SharedString::from(format!(\n                    \"number-state-{}-{}-{}\",\n                    options.page_ix, options.group_ix, options.item_ix\n                )),\n                cx,\n                |window, cx| {\n                    let input =\n                        cx.new(|cx| InputState::new(window, cx).default_value(value.to_string()));\n                    let _subscriptions = vec![\n                        cx.subscribe_in(&input, window, {\n                            move |_, input, event: &NumberInputEvent, window, cx| match event {\n                                NumberInputEvent::Step(action) => input.update(cx, |input, cx| {\n                                    let value = input.value();\n                                    if let Ok(value) = value.parse::<f64>() {\n                                        let new_value = if *action == StepAction::Increment {\n                                            value + num_options.step\n                                        } else {\n                                            value - num_options.step\n                                        };\n                                        input.set_value(\n                                            SharedString::from(new_value.to_string()),\n                                            window,\n                                            cx,\n                                        );\n                                    }\n                                }),\n                            }\n                        }),\n                        cx.subscribe_in(&input, window, {\n                            move |state: &mut State, input, event: &InputEvent, window, cx| {\n                                match event {\n                                    InputEvent::Change => {\n                                        input.update(cx, |input, cx| {\n                                            let value = input.value();\n                                            if value == state.initial_value.to_string() {\n                                                return;\n                                            }\n\n                                            if let Ok(value) = value.parse::<f64>() {\n                                                let clamp_value =\n                                                    value.clamp(num_options.min, num_options.max);\n\n                                                set_value(clamp_value, cx);\n                                                state.initial_value = clamp_value;\n                                                if clamp_value != value {\n                                                    input.set_value(\n                                                        SharedString::from(clamp_value.to_string()),\n                                                        window,\n                                                        cx,\n                                                    );\n                                                }\n                                            }\n                                        });\n                                    }\n                                    _ => {}\n                                }\n                            }\n                        }),\n                    ];\n\n                    State {\n                        input,\n                        initial_value: value,\n                        _subscriptions,\n                    }\n                },\n            )\n            .read(cx);\n\n        NumberInput::new(&state.input)\n            .with_size(options.size)\n            .map(|this| {\n                if options.layout.is_horizontal() {\n                    this.w_32()\n                } else {\n                    this.w_full()\n                }\n            })\n            .refine_style(style)\n            .into_any_element()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/setting/fields/string.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{\n    AnyElement, App, AppContext as _, Entity, IntoElement, SharedString, StyleRefinement, Styled,\n    Window, prelude::FluentBuilder as _,\n};\n\nuse crate::{\n    AxisExt as _, Sizable, StyledExt,\n    input::{Input, InputEvent, InputState},\n    setting::{\n        AnySettingField, RenderOptions,\n        fields::{SettingFieldRender, get_value, set_value},\n    },\n};\n\npub(crate) struct StringField<T> {\n    _marker: std::marker::PhantomData<T>,\n}\n\nimpl<T> StringField<T> {\n    pub(crate) fn new() -> Self {\n        Self {\n            _marker: std::marker::PhantomData,\n        }\n    }\n}\n\nstruct State {\n    input: Entity<InputState>,\n    _subscription: gpui::Subscription,\n}\n\nimpl<T> SettingFieldRender for StringField<T>\nwhere\n    T: Into<SharedString> + From<SharedString> + Clone + 'static,\n{\n    fn render(\n        &self,\n        field: Rc<dyn AnySettingField>,\n        options: &RenderOptions,\n        style: &StyleRefinement,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> AnyElement {\n        let value = get_value::<T>(&field, cx);\n        let set_value = set_value::<T>(&field, cx);\n\n        let state = window\n            .use_keyed_state(\n                SharedString::from(format!(\n                    \"string-state-{}-{}-{}\",\n                    options.page_ix, options.group_ix, options.item_ix\n                )),\n                cx,\n                |window, cx| {\n                    let input = cx.new(|cx| InputState::new(window, cx).default_value(value));\n                    let _subscription = cx.subscribe(&input, {\n                        move |_, input, event: &InputEvent, cx| match event {\n                            InputEvent::Change => {\n                                let value = input.read(cx).value();\n                                set_value(value.into(), cx);\n                            }\n                            _ => {}\n                        }\n                    });\n\n                    State {\n                        input,\n                        _subscription,\n                    }\n                },\n            )\n            .read(cx);\n\n        Input::new(&state.input)\n            .with_size(options.size)\n            .map(|this| {\n                if options.layout.is_horizontal() {\n                    this.w_64()\n                } else {\n                    this.w_full()\n                }\n            })\n            .refine_style(style)\n            .into_any_element()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/setting/group.rs",
    "content": "use gpui::{\n    App, IntoElement, ParentElement as _, SharedString, StyleRefinement, Styled, Window,\n    prelude::FluentBuilder as _,\n};\n\nuse crate::{\n    ActiveTheme, StyledExt,\n    group_box::{GroupBox, GroupBoxVariants},\n    label::Label,\n    setting::{RenderOptions, SettingItem},\n    v_flex,\n};\n\n/// A setting group that can contain multiple setting items.\n#[derive(Clone)]\npub struct SettingGroup {\n    style: StyleRefinement,\n\n    pub(super) title: Option<SharedString>,\n    pub(super) description: Option<SharedString>,\n    pub(super) items: Vec<SettingItem>,\n}\n\nimpl Styled for SettingGroup {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl SettingGroup {\n    /// Create a new setting group.\n    pub fn new() -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            title: None,\n            description: None,\n            items: Vec::new(),\n        }\n    }\n\n    /// Set the label of the setting group, default is None.\n    pub fn title(mut self, title: impl Into<SharedString>) -> Self {\n        self.title = Some(title.into());\n        self\n    }\n\n    /// Set the description of the setting group, default is None.\n    pub fn description(mut self, description: impl Into<SharedString>) -> Self {\n        self.description = Some(description.into());\n        self\n    }\n\n    /// Add a setting item to the group.\n    pub fn item(mut self, item: SettingItem) -> Self {\n        self.items.push(item);\n        self\n    }\n\n    /// Add multiple setting items to the group.\n    pub fn items<I>(mut self, items: I) -> Self\n    where\n        I: IntoIterator<Item = SettingItem>,\n    {\n        self.items.extend(items);\n        self\n    }\n\n    /// Return true if any of the setting items in the group match the given query.\n    pub(super) fn is_match(&self, query: &str, cx: &App) -> bool {\n        self.items.iter().any(|item| item.is_match(query, cx))\n    }\n\n    pub(super) fn is_resettable(&self, cx: &App) -> bool {\n        self.items.iter().any(|item| item.is_resettable(cx))\n    }\n\n    pub(crate) fn render(\n        self,\n        query: &str,\n        options: &RenderOptions,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> impl IntoElement {\n        GroupBox::new()\n            .id(SharedString::from(format!(\"group-{}\", options.group_ix)))\n            .with_variant(options.group_variant)\n            .when_some(self.title.clone(), |this, title| {\n                this.title(v_flex().gap_1().child(title).when_some(\n                    self.description.clone(),\n                    |this, description| {\n                        this.child(\n                            Label::new(description)\n                                .text_sm()\n                                .text_color(cx.theme().muted_foreground),\n                        )\n                    },\n                ))\n            })\n            .gap_4()\n            .children(self.items.iter().enumerate().filter_map(|(item_ix, item)| {\n                if item.is_match(&query, cx) {\n                    Some(item.clone().render_item(\n                        &RenderOptions {\n                            item_ix,\n                            ..*options\n                        },\n                        window,\n                        cx,\n                    ))\n                } else {\n                    None\n                }\n            }))\n            .refine_style(&self.style)\n    }\n\n    pub(crate) fn reset(&self, window: &mut Window, cx: &mut App) {\n        for item in &self.items {\n            item.reset(window, cx);\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/setting/item.rs",
    "content": "use gpui::{\n    AnyElement, App, Axis, Div, InteractiveElement as _, IntoElement, ParentElement, SharedString,\n    Stateful, Styled, Window, div, prelude::FluentBuilder as _,\n};\nuse std::{any::TypeId, ops::Deref, rc::Rc};\n\nuse crate::{\n    ActiveTheme as _, AxisExt, StyledExt as _,\n    label::Label,\n    setting::{\n        AnySettingField, ElementField, RenderOptions,\n        fields::{BoolField, DropdownField, NumberField, SettingFieldRender, StringField},\n    },\n    text::Text,\n    v_flex,\n};\n\n/// Setting item.\n#[derive(Clone)]\npub enum SettingItem {\n    /// A normal setting item with a title, description, and field.\n    Item {\n        title: SharedString,\n        description: Option<Text>,\n        layout: Axis,\n        field: Rc<dyn AnySettingField>,\n    },\n    /// A full custom element to render.\n    Element {\n        render: Rc<dyn Fn(&RenderOptions, &mut Window, &mut App) -> AnyElement + 'static>,\n    },\n}\n\nimpl SettingItem {\n    /// Create a new setting item.\n    pub fn new<F>(title: impl Into<SharedString>, field: F) -> Self\n    where\n        F: AnySettingField + 'static,\n    {\n        SettingItem::Item {\n            title: title.into(),\n            description: None,\n            layout: Axis::Horizontal,\n            field: Rc::new(field),\n        }\n    }\n\n    /// Create a new custom element setting item with a render closure.\n    pub fn render<R, E>(render: R) -> Self\n    where\n        E: IntoElement,\n        R: Fn(&RenderOptions, &mut Window, &mut App) -> E + 'static,\n    {\n        SettingItem::Element {\n            render: Rc::new(move |options, window, cx| {\n                render(options, window, cx).into_any_element()\n            }),\n        }\n    }\n\n    /// Set the description of the setting item.\n    ///\n    /// Only applies to [`SettingItem::Item`].\n    pub fn description(mut self, description: impl Into<Text>) -> Self {\n        match &mut self {\n            SettingItem::Item { description: d, .. } => {\n                *d = Some(description.into());\n            }\n            SettingItem::Element { .. } => {}\n        }\n        self\n    }\n\n    /// Set the layout of the setting item.\n    ///\n    /// Only applies to [`SettingItem::Item`].\n    pub fn layout(mut self, layout: Axis) -> Self {\n        match &mut self {\n            SettingItem::Item { layout: l, .. } => {\n                *l = layout;\n            }\n            SettingItem::Element { .. } => {}\n        }\n        self\n    }\n\n    pub(crate) fn is_match(&self, query: &str, cx: &App) -> bool {\n        match self {\n            SettingItem::Item {\n                title, description, ..\n            } => {\n                title.to_lowercase().contains(&query.to_lowercase())\n                    || description.as_ref().map_or(false, |d| {\n                        d.get_text(cx)\n                            .to_lowercase()\n                            .contains(&query.to_lowercase())\n                    })\n            }\n            // We need to show all custom elements when not searching.\n            SettingItem::Element { .. } => query.is_empty(),\n        }\n    }\n\n    pub(crate) fn is_resettable(&self, cx: &App) -> bool {\n        match self {\n            SettingItem::Item { field, .. } => field.is_resettable(cx),\n            SettingItem::Element { .. } => false,\n        }\n    }\n\n    pub(crate) fn reset(&self, window: &mut Window, cx: &mut App) {\n        match self {\n            SettingItem::Item { field, .. } => field.reset(window, cx),\n            SettingItem::Element { .. } => {}\n        }\n    }\n\n    fn render_field(\n        field: Rc<dyn AnySettingField>,\n        options: RenderOptions,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> impl IntoElement {\n        let field_type = field.field_type();\n        let style = field.style().clone();\n        let type_id = field.deref().type_id();\n        let renderer: Box<dyn SettingFieldRender> = match type_id {\n            t if t == std::any::TypeId::of::<bool>() => {\n                Box::new(BoolField::new(field_type.is_switch()))\n            }\n            t if t == TypeId::of::<f64>() && field_type.is_number_input() => {\n                Box::new(NumberField::new(field_type.number_input_options()))\n            }\n            t if t == TypeId::of::<SharedString>() && field_type.is_input() => {\n                Box::new(StringField::<SharedString>::new())\n            }\n            t if t == TypeId::of::<String>() && field_type.is_input() => {\n                Box::new(StringField::<String>::new())\n            }\n            t if t == TypeId::of::<SharedString>() && field_type.is_dropdown() => Box::new(\n                DropdownField::<SharedString>::new(field_type.dropdown_options()),\n            ),\n            t if t == TypeId::of::<String>() && field_type.is_dropdown() => {\n                Box::new(DropdownField::<String>::new(field_type.dropdown_options()))\n            }\n            _ if field_type.is_element() => Box::new(ElementField::new(field_type.element())),\n            _ => unimplemented!(\"Unsupported setting type: {}\", field.deref().type_name()),\n        };\n\n        renderer.render(field, &options, &style, window, cx)\n    }\n\n    pub(super) fn render_item(\n        self,\n        options: &RenderOptions,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Stateful<Div> {\n        div()\n            .id(SharedString::from(format!(\"item-{}\", options.item_ix)))\n            .w_full()\n            .child(match self {\n                SettingItem::Item {\n                    title,\n                    description,\n                    layout,\n                    field,\n                } => div()\n                    .w_full()\n                    .overflow_hidden()\n                    .map(|this| {\n                        if layout.is_horizontal() {\n                            this.h_flex().justify_between().items_start()\n                        } else {\n                            this.v_flex()\n                        }\n                    })\n                    .gap_3()\n                    .child(\n                        v_flex()\n                            .map(|this| {\n                                if layout.is_horizontal() {\n                                    this.flex_1().max_w_3_5()\n                                } else {\n                                    this.w_full()\n                                }\n                            })\n                            .gap_1()\n                            .child(Label::new(title.clone()).text_sm())\n                            .when_some(description.clone(), |this, description| {\n                                this.child(\n                                    div()\n                                        .size_full()\n                                        .text_sm()\n                                        .text_color(cx.theme().muted_foreground)\n                                        .child(description),\n                                )\n                            }),\n                    )\n                    .child(div().id(\"field\").child(Self::render_field(\n                        field,\n                        RenderOptions { layout, ..*options },\n                        window,\n                        cx,\n                    )))\n                    .into_any_element(),\n                SettingItem::Element { render } => {\n                    (render)(&options, window, cx).into_any_element()\n                }\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/setting/mod.rs",
    "content": "mod fields;\nmod group;\nmod item;\nmod page;\nmod settings;\n\npub use fields::*;\npub use group::*;\npub use item::*;\npub use page::*;\npub use settings::*;\n"
  },
  {
    "path": "crates/ui/src/setting/page.rs",
    "content": "use gpui::{\n    App, Entity, InteractiveElement as _, IntoElement, ListAlignment, ListState,\n    ParentElement as _, SharedString, StyleRefinement, Styled, Window, div, list,\n    prelude::FluentBuilder as _, px,\n};\nuse rust_i18n::t;\n\nuse crate::{\n    ActiveTheme, Icon, IconName, Sizable, StyledExt,\n    button::{Button, ButtonVariants},\n    h_flex,\n    label::Label,\n    scroll::ScrollableElement,\n    setting::{RenderOptions, SettingGroup, settings::SettingsState},\n    v_flex,\n};\n\n/// A setting page that can contain multiple setting groups.\n#[derive(Clone)]\npub struct SettingPage {\n    pub(super) icon: Option<Icon>,\n    resettable: bool,\n    pub(super) default_open: bool,\n    pub(super) title: SharedString,\n    pub(super) description: Option<SharedString>,\n    pub(super) groups: Vec<SettingGroup>,\n    pub(super) header_style: StyleRefinement,\n}\n\nimpl SettingPage {\n    pub fn new(title: impl Into<SharedString>) -> Self {\n        Self {\n            icon: None,\n            resettable: true,\n            default_open: false,\n            title: title.into(),\n            description: None,\n            groups: Vec::new(),\n            header_style: StyleRefinement::default(),\n        }\n    }\n\n    /// Set the title of the setting page.\n    pub fn title(mut self, title: impl Into<SharedString>) -> Self {\n        self.title = title.into();\n        self\n    }\n\n    /// Set the icon of the setting page.\n    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {\n        self.icon = Some(icon.into());\n        self\n    }\n\n    /// Set the description of the setting page, default is None.\n    pub fn description(mut self, description: impl Into<SharedString>) -> Self {\n        self.description = Some(description.into());\n        self\n    }\n\n    /// Set the default open state of the setting page, default is false.\n    pub fn default_open(mut self, default_open: bool) -> Self {\n        self.default_open = default_open;\n        self\n    }\n\n    /// Set whether the setting page is resettable, default is true.\n    ///\n    /// If true and the items in this page has changed, the reset button will appear.\n    pub fn resettable(mut self, resettable: bool) -> Self {\n        self.resettable = resettable;\n        self\n    }\n\n    /// Add a setting group to the page.\n    pub fn group(mut self, group: SettingGroup) -> Self {\n        self.groups.push(group);\n        self\n    }\n\n    /// Add multiple setting groups to the page.\n    pub fn groups(mut self, groups: impl IntoIterator<Item = SettingGroup>) -> Self {\n        self.groups.extend(groups);\n        self\n    }\n\n    /// Set the style refinement for the header of the setting page.\n    pub fn header_style(mut self, style: &StyleRefinement) -> Self {\n        self.header_style = style.clone();\n        self\n    }\n\n    fn is_resettable(&self, cx: &App) -> bool {\n        self.resettable && self.groups.iter().any(|group| group.is_resettable(cx))\n    }\n\n    fn reset_all(&self, window: &mut Window, cx: &mut App) {\n        for group in &self.groups {\n            group.reset(window, cx);\n        }\n    }\n\n    pub(super) fn render(\n        &self,\n        ix: usize,\n        state: &Entity<SettingsState>,\n        options: &RenderOptions,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> impl IntoElement {\n        let search_input = state.read(cx).search_input.clone();\n        let query = search_input.read(cx).value();\n        let groups = self\n            .groups\n            .iter()\n            .filter(|group| group.is_match(&query, cx))\n            .cloned()\n            .collect::<Vec<_>>();\n        let groups_count = groups.len();\n\n        let list_state = window\n            .use_keyed_state(\n                SharedString::from(format!(\"list-state:{}\", ix)),\n                cx,\n                |_, _| ListState::new(groups_count, ListAlignment::Top, px(100.)),\n            )\n            .read(cx)\n            .clone();\n\n        if list_state.item_count() != groups_count {\n            list_state.reset(groups_count);\n        }\n\n        let deferred_scroll_group_ix = state.read(cx).deferred_scroll_group_ix;\n        if let Some(ix) = deferred_scroll_group_ix {\n            state.update(cx, |state, _| {\n                state.deferred_scroll_group_ix = None;\n            });\n            list_state.scroll_to_reveal_item(ix);\n        }\n\n        v_flex()\n            .id(ix)\n            .size_full()\n            .child(\n                v_flex()\n                    .p_4()\n                    .gap_3()\n                    .border_b_1()\n                    .border_color(cx.theme().border)\n                    .refine_style(&self.header_style)\n                    .child(h_flex().justify_between().child(self.title.clone()).when(\n                        self.is_resettable(cx),\n                        |this| {\n                            this.child(\n                                Button::new(\"reset\")\n                                    .icon(IconName::Undo2)\n                                    .ghost()\n                                    .small()\n                                    .tooltip(t!(\"Settings.Reset All\"))\n                                    .on_click({\n                                        let page = self.clone();\n                                        move |_, window, cx| {\n                                            page.reset_all(window, cx);\n                                        }\n                                    }),\n                            )\n                        },\n                    ))\n                    .when_some(self.description.clone(), |this, description| {\n                        this.child(\n                            Label::new(description)\n                                .text_sm()\n                                .text_color(cx.theme().muted_foreground),\n                        )\n                    }),\n            )\n            .child(\n                div()\n                    .px_4()\n                    .relative()\n                    .flex_1()\n                    .w_full()\n                    .child(\n                        list(list_state.clone(), {\n                            let query = query.clone();\n                            let options = *options;\n                            move |group_ix, window, cx| {\n                                let group = groups[group_ix].clone();\n                                group\n                                    .py_4()\n                                    .render(\n                                        &query,\n                                        &RenderOptions {\n                                            page_ix: ix,\n                                            group_ix,\n                                            ..options\n                                        },\n                                        window,\n                                        cx,\n                                    )\n                                    .into_any_element()\n                            }\n                        })\n                        .size_full(),\n                    )\n                    .vertical_scrollbar(&list_state),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/setting/settings.rs",
    "content": "use crate::{\n    IconName, Sizable, Size, StyledExt,\n    group_box::GroupBoxVariant,\n    input::{Input, InputState},\n    resizable::{h_resizable, resizable_panel},\n    setting::{SettingGroup, SettingPage},\n    sidebar::{Sidebar, SidebarMenu, SidebarMenuItem},\n};\nuse gpui::{\n    App, AppContext as _, Axis, ElementId, Entity, IntoElement, ParentElement as _, Pixels,\n    RenderOnce, StyleRefinement, Styled, Window, div, prelude::FluentBuilder as _, px, relative,\n};\nuse rust_i18n::t;\n\n/// The settings structure containing multiple pages for app settings.\n///\n/// The hierarchy of settings is as follows:\n///\n/// ```ignore\n/// Settings\n///   SettingPage     <- The single active page displayed\n///     SettingGroup\n///       SettingItem\n///         Label\n///         SettingField (e.g., Switch, Dropdown, Input)\n/// ```\n#[derive(IntoElement)]\npub struct Settings {\n    id: ElementId,\n    pages: Vec<SettingPage>,\n    group_variant: GroupBoxVariant,\n    size: Size,\n    sidebar_width: Pixels,\n    sidebar_style: StyleRefinement,\n    default_selected_index: SelectIndex,\n    header_style: StyleRefinement,\n}\n\nimpl Settings {\n    /// Create a new settings with the given ID.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            pages: vec![],\n            group_variant: GroupBoxVariant::default(),\n            size: Size::default(),\n            sidebar_width: px(250.0),\n            sidebar_style: StyleRefinement::default(),\n            default_selected_index: SelectIndex::default(),\n            header_style: StyleRefinement::default(),\n        }\n    }\n\n    /// Set the width of the sidebar, default is `250px`.\n    pub fn sidebar_width(mut self, width: impl Into<Pixels>) -> Self {\n        self.sidebar_width = width.into();\n        self\n    }\n\n    /// Add a page to the settings.\n    pub fn page(mut self, page: SettingPage) -> Self {\n        self.pages.push(page);\n        self\n    }\n\n    /// Add pages to the settings.\n    pub fn pages(mut self, pages: impl IntoIterator<Item = SettingPage>) -> Self {\n        self.pages.extend(pages);\n        self\n    }\n\n    /// Set the default variant for all setting groups.\n    ///\n    /// All setting groups will use this variant unless overridden individually.\n    pub fn with_group_variant(mut self, variant: GroupBoxVariant) -> Self {\n        self.group_variant = variant;\n        self\n    }\n\n    /// Set the style refinement for the sidebar.\n    pub fn sidebar_style(mut self, style: &StyleRefinement) -> Self {\n        self.sidebar_style = style.clone();\n        self\n    }\n\n    /// Set the default index of the page to be selected.\n    pub fn default_selected_index(mut self, index: SelectIndex) -> Self {\n        self.default_selected_index = index;\n        self\n    }\n\n    /// Set the style refinement for the header.\n    pub fn header_style(mut self, style: &StyleRefinement) -> Self {\n        self.header_style = style.clone();\n        self\n    }\n\n    fn filtered_pages(&self, query: &str, cx: &App) -> Vec<SettingPage> {\n        self.pages\n            .iter()\n            .filter_map(|page| {\n                let filtered_groups: Vec<SettingGroup> = page\n                    .groups\n                    .iter()\n                    .filter_map(|group| {\n                        let mut group = group.clone();\n                        group.items = group\n                            .items\n                            .iter()\n                            .filter(|item| item.is_match(&query, cx))\n                            .cloned()\n                            .collect();\n                        if group.items.is_empty() {\n                            None\n                        } else {\n                            Some(group)\n                        }\n                    })\n                    .collect();\n                let mut page = page.clone();\n                page.groups = filtered_groups;\n                if page.groups.is_empty() {\n                    None\n                } else {\n                    Some(page)\n                }\n            })\n            .collect()\n    }\n\n    fn render_active_page(\n        &self,\n        state: &Entity<SettingsState>,\n        pages: &Vec<SettingPage>,\n        options: &RenderOptions,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> impl IntoElement {\n        let selected_index = state.read(cx).selected_index;\n\n        for (ix, page) in pages.into_iter().enumerate() {\n            if selected_index.page_ix == ix {\n                return page\n                    .render(ix, state, &options, window, cx)\n                    .into_any_element();\n            }\n        }\n\n        return div().into_any_element();\n    }\n\n    fn render_sidebar(\n        &self,\n        state: &Entity<SettingsState>,\n        pages: &Vec<SettingPage>,\n        _: &mut Window,\n        cx: &mut App,\n    ) -> impl IntoElement {\n        let selected_index = state.read(cx).selected_index;\n        let search_input = state.read(cx).search_input.clone();\n\n        Sidebar::new(\"settings-sidebar\")\n            .w(relative(1.))\n            .border_0()\n            .refine_style(&self.sidebar_style)\n            .collapsed(false)\n            .header(\n                div()\n                    .w_full()\n                    .refine_style(&self.header_style)\n                    .child(Input::new(&search_input).prefix(IconName::Search)),\n            )\n            .child(\n                SidebarMenu::new().children(pages.iter().enumerate().map(|(page_ix, page)| {\n                    let is_page_active =\n                        selected_index.page_ix == page_ix && selected_index.group_ix.is_none();\n                    SidebarMenuItem::new(page.title.clone())\n                        .when_some(page.icon.clone(), |this, icon| this.icon(icon))\n                        .default_open(page.default_open)\n                        .active(is_page_active)\n                        .on_click({\n                            let state = state.clone();\n                            move |_, _, cx| {\n                                state.update(cx, |state, cx| {\n                                    state.selected_index = SelectIndex {\n                                        page_ix,\n                                        ..Default::default()\n                                    };\n                                    cx.notify();\n                                })\n                            }\n                        })\n                        .when(page.groups.len() > 1, |this| {\n                            this.children(\n                                page.groups\n                                    .iter()\n                                    .filter(|g| g.title.is_some())\n                                    .enumerate()\n                                    .map(|(group_ix, group)| {\n                                        let is_active = selected_index.page_ix == page_ix\n                                            && selected_index.group_ix == Some(group_ix);\n                                        let title = group.title.clone().unwrap_or_default();\n\n                                        SidebarMenuItem::new(title).active(is_active).on_click({\n                                            let state = state.clone();\n                                            move |_, _, cx| {\n                                                state.update(cx, |state, cx| {\n                                                    state.selected_index = SelectIndex {\n                                                        page_ix,\n                                                        group_ix: Some(group_ix),\n                                                    };\n                                                    state.deferred_scroll_group_ix = Some(group_ix);\n                                                    cx.notify();\n                                                })\n                                            }\n                                        })\n                                    }),\n                            )\n                        })\n                })),\n            )\n    }\n}\n\nimpl Sizable for Settings {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\npub(super) struct SettingsState {\n    pub(super) selected_index: SelectIndex,\n    /// If set, defer scrolling to this group index after rendering.\n    pub(super) deferred_scroll_group_ix: Option<usize>,\n    pub(super) search_input: Entity<InputState>,\n}\n\n/// Options for rendering setting item.\n#[derive(Clone, Copy)]\npub struct RenderOptions {\n    pub page_ix: usize,\n    pub group_ix: usize,\n    pub item_ix: usize,\n    pub size: Size,\n    pub group_variant: GroupBoxVariant,\n    pub layout: Axis,\n}\n\n#[derive(Clone, Copy, Default)]\npub struct SelectIndex {\n    pub page_ix: usize,\n    pub group_ix: Option<usize>,\n}\n\nimpl RenderOnce for Settings {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let state = window.use_keyed_state(self.id.clone(), cx, |window, cx| {\n            let search_input = cx.new(|cx| {\n                InputState::new(window, cx)\n                    .placeholder(t!(\"Settings.search_placeholder\"))\n                    .default_value(\"\")\n            });\n\n            SettingsState {\n                search_input,\n                selected_index: self.default_selected_index,\n                deferred_scroll_group_ix: None,\n            }\n        });\n\n        let query = state.read(cx).search_input.read(cx).value();\n        let filtered_pages = self.filtered_pages(&query, cx);\n        let options = RenderOptions {\n            page_ix: 0,\n            group_ix: 0,\n            item_ix: 0,\n            size: self.size,\n            group_variant: self.group_variant,\n            layout: Axis::Horizontal,\n        };\n\n        h_resizable(self.id.clone())\n            .child(\n                resizable_panel()\n                    .size(self.sidebar_width)\n                    .child(self.render_sidebar(&state, &filtered_pages, window, cx)),\n            )\n            .child(resizable_panel().child(self.render_active_page(\n                &state,\n                &filtered_pages,\n                &options,\n                window,\n                cx,\n            )))\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/sheet.rs",
    "content": "use std::{rc::Rc, time::Duration};\n\nuse gpui::{\n    Animation, AnimationExt as _, AnyElement, App, ClickEvent, DefiniteLength, DismissEvent, Edges,\n    EventEmitter, FocusHandle, InteractiveElement as _, IntoElement, KeyBinding, MouseButton,\n    ParentElement, Pixels, RenderOnce, StyleRefinement, Styled, Window, WindowControlArea,\n    anchored, div, point, prelude::FluentBuilder as _, px,\n};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\nuse crate::{\n    ActiveTheme, FocusTrapElement as _, IconName, Placement, Sizable, StyledExt as _,\n    WindowExt as _,\n    actions::Cancel,\n    button::{Button, ButtonVariants as _},\n    dialog::overlay_color,\n    h_flex,\n    scroll::ScrollableElement as _,\n    title_bar::TITLE_BAR_HEIGHT,\n    v_flex,\n};\n\nconst CONTEXT: &str = \"Sheet\";\npub(crate) fn init(cx: &mut App) {\n    cx.bind_keys([KeyBinding::new(\"escape\", Cancel, Some(CONTEXT))])\n}\n\n/// The settings for sheets.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct SheetSettings {\n    /// The margin top for the sheet, default is [`TITLE_BAR_HEIGHT`].\n    pub margin_top: Pixels,\n}\n\nimpl Default for SheetSettings {\n    fn default() -> Self {\n        Self {\n            margin_top: TITLE_BAR_HEIGHT,\n        }\n    }\n}\n\n/// Sheet component that slides in from the side of the window.\n#[derive(IntoElement)]\npub struct Sheet {\n    pub(crate) focus_handle: FocusHandle,\n    pub(crate) placement: Placement,\n    pub(crate) size: DefiniteLength,\n    resizable: bool,\n    on_close: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,\n    title: Option<AnyElement>,\n    footer: Option<AnyElement>,\n    style: StyleRefinement,\n    children: Vec<AnyElement>,\n    overlay: bool,\n    overlay_closable: bool,\n}\n\nimpl Sheet {\n    /// Creates a new Sheet.\n    pub fn new(_: &mut Window, cx: &mut App) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n            placement: Placement::Right,\n            size: DefiniteLength::Absolute(px(350.).into()),\n            resizable: true,\n            title: None,\n            footer: None,\n            style: StyleRefinement::default(),\n            children: Vec::new(),\n            overlay: true,\n            overlay_closable: true,\n            on_close: Rc::new(|_, _, _| {}),\n        }\n    }\n\n    /// Sets the title of the sheet.\n    pub fn title(mut self, title: impl IntoElement) -> Self {\n        self.title = Some(title.into_any_element());\n        self\n    }\n\n    /// Set the footer of the sheet.\n    pub fn footer(mut self, footer: impl IntoElement) -> Self {\n        self.footer = Some(footer.into_any_element());\n        self\n    }\n\n    /// Sets the size of the sheet, default is 350px.\n    pub fn size(mut self, size: impl Into<DefiniteLength>) -> Self {\n        self.size = size.into();\n        self\n    }\n\n    /// Sets whether the sheet is resizable, default is `true`.\n    pub fn resizable(mut self, resizable: bool) -> Self {\n        self.resizable = resizable;\n        self\n    }\n\n    /// Set whether the sheet should have an overlay, default is `true`.\n    pub fn overlay(mut self, overlay: bool) -> Self {\n        self.overlay = overlay;\n        self\n    }\n\n    /// Set whether the sheet should be closable by clicking the overlay, default is `true`.\n    pub fn overlay_closable(mut self, overlay_closable: bool) -> Self {\n        self.overlay_closable = overlay_closable;\n        self\n    }\n\n    /// Listen to the close event of the sheet.\n    pub fn on_close(\n        mut self,\n        on_close: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_close = Rc::new(on_close);\n        self\n    }\n}\n\nimpl EventEmitter<DismissEvent> for Sheet {}\nimpl ParentElement for Sheet {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\nimpl Styled for Sheet {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Sheet {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let placement = self.placement;\n        let window_paddings = crate::window_border::window_paddings(window);\n        let size = window.viewport_size()\n            - gpui::size(\n                window_paddings.left + window_paddings.right,\n                window_paddings.top + window_paddings.bottom,\n            );\n        let top = cx.theme().sheet.margin_top;\n        let on_close = self.on_close.clone();\n\n        let base_size = window.text_style().font_size;\n        let rem_size = window.rem_size();\n        let mut paddings = Edges::all(px(16.));\n        if let Some(pl) = self.style.padding.left {\n            paddings.left = pl.to_pixels(base_size, rem_size);\n        }\n        if let Some(pr) = self.style.padding.right {\n            paddings.right = pr.to_pixels(base_size, rem_size);\n        }\n        if let Some(pt) = self.style.padding.top {\n            paddings.top = pt.to_pixels(base_size, rem_size);\n        }\n        if let Some(pb) = self.style.padding.bottom {\n            paddings.bottom = pb.to_pixels(base_size, rem_size);\n        }\n\n        anchored()\n            .position(point(window_paddings.left, window_paddings.top))\n            .snap_to_window()\n            .child(\n                div()\n                    .occlude()\n                    .w(size.width)\n                    .h(size.height)\n                    .bg(overlay_color(self.overlay, cx))\n                    .when(self.overlay, |this| {\n                        this.window_control_area(WindowControlArea::Drag)\n                            .on_any_mouse_down({\n                                let on_close = self.on_close.clone();\n                                move |event, window, cx| {\n                                    if event.position.y < top {\n                                        return;\n                                    }\n\n                                    cx.stop_propagation();\n                                    if self.overlay_closable && event.button == MouseButton::Left {\n                                        window.close_sheet(cx);\n                                        on_close(&ClickEvent::default(), window, cx);\n                                    }\n                                }\n                            })\n                    })\n                    .child(\n                        v_flex()\n                            .id(\"sheet\")\n                            .key_context(CONTEXT)\n                            .track_focus(&self.focus_handle)\n                            .focus_trap(\"sheet\", &self.focus_handle)\n                            .on_action({\n                                let on_close = self.on_close.clone();\n                                move |_: &Cancel, window, cx| {\n                                    cx.propagate();\n\n                                    window.close_sheet(cx);\n                                    on_close(&ClickEvent::default(), window, cx);\n                                }\n                            })\n                            .absolute()\n                            .occlude()\n                            .bg(cx.theme().background)\n                            .border_color(cx.theme().border)\n                            .shadow_xl()\n                            .refine_style(&self.style)\n                            .map(|this| {\n                                // Set the size of the sheet.\n                                if placement.is_horizontal() {\n                                    this.w(self.size)\n                                } else {\n                                    this.h(self.size)\n                                }\n                            })\n                            .map(|this| match self.placement {\n                                Placement::Top => this.top(top).left_0().right_0().border_b_1(),\n                                Placement::Right => this.top(top).right_0().bottom_0().border_l_1(),\n                                Placement::Bottom => {\n                                    this.bottom_0().left_0().right_0().border_t_1()\n                                }\n                                Placement::Left => this.top(top).left_0().bottom_0().border_r_1(),\n                            })\n                            .child(\n                                // TitleBar\n                                h_flex()\n                                    .justify_between()\n                                    .pl_4()\n                                    .pr_3()\n                                    .py_2()\n                                    .w_full()\n                                    .font_semibold()\n                                    .child(self.title.unwrap_or(div().into_any_element()))\n                                    .child(\n                                        Button::new(\"close\")\n                                            .small()\n                                            .ghost()\n                                            .icon(IconName::Close)\n                                            .on_click(move |_, window, cx| {\n                                                window.close_sheet(cx);\n                                                on_close(&ClickEvent::default(), window, cx);\n                                            }),\n                                    ),\n                            )\n                            .child(\n                                div().flex_1().overflow_hidden().child(\n                                    // Body\n                                    v_flex()\n                                        .size_full()\n                                        .overflow_y_scrollbar()\n                                        .pl(paddings.left)\n                                        .pr(paddings.right)\n                                        .children(self.children),\n                                ),\n                            )\n                            .when_some(self.footer, |this, footer| {\n                                // Footer\n                                this.child(\n                                    h_flex()\n                                        .justify_between()\n                                        .px_4()\n                                        .py_3()\n                                        .w_full()\n                                        .child(footer),\n                                )\n                            })\n                            .on_any_mouse_down({\n                                |_, _, cx| {\n                                    cx.stop_propagation();\n                                }\n                            })\n                            .with_animation(\n                                \"slide\",\n                                Animation::new(Duration::from_secs_f64(0.15)),\n                                move |this, delta| {\n                                    let y = px(-100.) + delta * px(100.);\n                                    this.map(|this| match placement {\n                                        Placement::Top => this.top(top + y),\n                                        Placement::Right => this.right(y),\n                                        Placement::Bottom => this.bottom(y),\n                                        Placement::Left => this.left(y),\n                                    })\n                                },\n                            ),\n                    ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/sidebar/footer.rs",
    "content": "use gpui::{\n    Div, InteractiveElement, IntoElement, ParentElement, RenderOnce, Styled,\n    prelude::FluentBuilder as _,\n};\n\nuse crate::{ActiveTheme as _, Collapsible, Selectable, h_flex, menu::DropdownMenu};\n\n/// Footer for the [`super::Sidebar`].\n#[derive(IntoElement)]\npub struct SidebarFooter {\n    base: Div,\n    selected: bool,\n    collapsed: bool,\n}\n\nimpl SidebarFooter {\n    /// Create a new [`SidebarFooter`].\n    pub fn new() -> Self {\n        Self {\n            base: h_flex().gap_2().w_full(),\n            selected: false,\n            collapsed: false,\n        }\n    }\n}\n\nimpl Selectable for SidebarFooter {\n    fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        self.selected\n    }\n}\n\nimpl Collapsible for SidebarFooter {\n    fn is_collapsed(&self) -> bool {\n        self.collapsed\n    }\n\n    fn collapsed(mut self, collapsed: bool) -> Self {\n        self.collapsed = collapsed;\n        self\n    }\n}\n\nimpl ParentElement for SidebarFooter {\n    fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {\n        self.base.extend(elements);\n    }\n}\n\nimpl Styled for SidebarFooter {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        self.base.style()\n    }\n}\n\nimpl InteractiveElement for SidebarFooter {\n    fn interactivity(&mut self) -> &mut gpui::Interactivity {\n        self.base.interactivity()\n    }\n}\n\nimpl DropdownMenu for SidebarFooter {}\n\nimpl RenderOnce for SidebarFooter {\n    fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement {\n        h_flex()\n            .id(\"sidebar-footer\")\n            .gap_2()\n            .p_2()\n            .w_full()\n            .justify_between()\n            .rounded(cx.theme().radius)\n            .hover(|this| {\n                this.bg(cx.theme().sidebar_accent)\n                    .text_color(cx.theme().sidebar_accent_foreground)\n            })\n            .when(self.selected, |this| {\n                this.bg(cx.theme().sidebar_accent)\n                    .text_color(cx.theme().sidebar_accent_foreground)\n            })\n            .child(self.base)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/sidebar/group.rs",
    "content": "use crate::{ActiveTheme, Collapsible, h_flex, sidebar::SidebarItem, v_flex};\nuse gpui::{\n    App, ElementId, IntoElement, ParentElement, SharedString, Styled as _, Window, div,\n    prelude::FluentBuilder as _,\n};\n\n/// A group of items in the [`super::Sidebar`].\n#[derive(Clone)]\npub struct SidebarGroup<E: SidebarItem + 'static> {\n    label: SharedString,\n    collapsed: bool,\n    children: Vec<E>,\n}\n\nimpl<E: SidebarItem> SidebarGroup<E> {\n    /// Create a new [`SidebarGroup`] with the given label.\n    pub fn new(label: impl Into<SharedString>) -> Self {\n        Self {\n            label: label.into(),\n            collapsed: false,\n            children: Vec::new(),\n        }\n    }\n\n    /// Add a child to the sidebar group, the child should implement [`SidebarItem`].\n    pub fn child(mut self, child: E) -> Self {\n        self.children.push(child);\n        self\n    }\n\n    /// Add multiple children to the sidebar group.\n    ///\n    /// See also [`SidebarGroup::child`].\n    pub fn children(mut self, children: impl IntoIterator<Item = E>) -> Self {\n        self.children.extend(children);\n        self\n    }\n}\n\nimpl<E: SidebarItem> Collapsible for SidebarGroup<E> {\n    fn is_collapsed(&self) -> bool {\n        self.collapsed\n    }\n\n    fn collapsed(mut self, collapsed: bool) -> Self {\n        self.collapsed = collapsed;\n        self\n    }\n}\n\nimpl<E: SidebarItem> SidebarItem for SidebarGroup<E> {\n    fn render(\n        self,\n        id: impl Into<ElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> impl IntoElement {\n        let id = id.into();\n\n        v_flex()\n            .relative()\n            .when(!self.collapsed, |this| {\n                this.child(\n                    h_flex()\n                        .flex_shrink_0()\n                        .px_2()\n                        .rounded(cx.theme().radius)\n                        .text_xs()\n                        .text_color(cx.theme().sidebar_foreground.opacity(0.7))\n                        .h_8()\n                        .child(self.label),\n                )\n            })\n            .child(\n                div()\n                    .gap_2()\n                    .flex_col()\n                    .children(self.children.into_iter().enumerate().map(|(ix, child)| {\n                        child\n                            .collapsed(self.collapsed)\n                            .render(format!(\"{}-{}\", id, ix), window, cx)\n                            .into_any_element()\n                    })),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/sidebar/header.rs",
    "content": "use gpui::{\n    AnyElement, Div, InteractiveElement, IntoElement, ParentElement, RenderOnce, StyleRefinement,\n    Styled, div, prelude::FluentBuilder as _,\n};\n\nuse crate::{ActiveTheme as _, Collapsible, Selectable, StyledExt, menu::DropdownMenu};\n\n/// Header for the [`super::Sidebar`]\n#[derive(IntoElement)]\npub struct SidebarHeader {\n    base: Div,\n    style: StyleRefinement,\n    children: Vec<AnyElement>,\n    selected: bool,\n    collapsed: bool,\n}\n\nimpl SidebarHeader {\n    /// Create a new [`SidebarHeader`].\n    pub fn new() -> Self {\n        Self {\n            base: div(),\n            style: StyleRefinement::default(),\n            children: Vec::new(),\n            selected: false,\n            collapsed: false,\n        }\n    }\n}\n\nimpl Default for SidebarHeader {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Selectable for SidebarHeader {\n    fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        self.selected\n    }\n}\n\nimpl Collapsible for SidebarHeader {\n    fn is_collapsed(&self) -> bool {\n        self.collapsed\n    }\n\n    fn collapsed(mut self, collapsed: bool) -> Self {\n        self.collapsed = collapsed;\n        self\n    }\n}\n\nimpl ParentElement for SidebarHeader {\n    fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Styled for SidebarHeader {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl InteractiveElement for SidebarHeader {\n    fn interactivity(&mut self) -> &mut gpui::Interactivity {\n        self.base.interactivity()\n    }\n}\n\nimpl DropdownMenu for SidebarHeader {}\n\nimpl RenderOnce for SidebarHeader {\n    fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement {\n        self.base\n            .id(\"sidebar-header\")\n            .h_flex()\n            .gap_2()\n            .p_2()\n            .w_full()\n            .justify_between()\n            .rounded(cx.theme().radius)\n            .refine_style(&self.style)\n            .hover(|this| {\n                this.bg(cx.theme().sidebar_accent)\n                    .text_color(cx.theme().sidebar_accent_foreground)\n            })\n            .when(self.selected, |this| {\n                this.bg(cx.theme().sidebar_accent)\n                    .text_color(cx.theme().sidebar_accent_foreground)\n            })\n            .children(self.children)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/sidebar/menu.rs",
    "content": "use crate::{\n    ActiveTheme as _, Collapsible, Icon, IconName, Sizable as _, StyledExt,\n    button::{Button, ButtonVariants as _},\n    h_flex,\n    menu::{ContextMenuExt, PopupMenu},\n    sidebar::SidebarItem,\n    v_flex,\n};\nuse gpui::{\n    AnyElement, App, ClickEvent, ElementId, InteractiveElement as _, IntoElement,\n    ParentElement as _, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled,\n    Window, div, percentage, prelude::FluentBuilder,\n};\nuse std::rc::Rc;\n\n/// Menu for the [`super::Sidebar`]\n#[derive(Clone)]\npub struct SidebarMenu {\n    style: StyleRefinement,\n    collapsed: bool,\n    items: Vec<SidebarMenuItem>,\n}\n\nimpl SidebarMenu {\n    /// Create a new SidebarMenu\n    pub fn new() -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            items: Vec::new(),\n            collapsed: false,\n        }\n    }\n\n    /// Add a [`SidebarMenuItem`] child menu item to the sidebar menu.\n    ///\n    /// See also [`SidebarMenu::children`].\n    pub fn child(mut self, child: impl Into<SidebarMenuItem>) -> Self {\n        self.items.push(child.into());\n        self\n    }\n\n    /// Add multiple [`SidebarMenuItem`] child menu items to the sidebar menu.\n    pub fn children(\n        mut self,\n        children: impl IntoIterator<Item = impl Into<SidebarMenuItem>>,\n    ) -> Self {\n        self.items = children.into_iter().map(Into::into).collect();\n        self\n    }\n}\n\nimpl Collapsible for SidebarMenu {\n    fn is_collapsed(&self) -> bool {\n        self.collapsed\n    }\n\n    fn collapsed(mut self, collapsed: bool) -> Self {\n        self.collapsed = collapsed;\n        self\n    }\n}\n\nimpl SidebarItem for SidebarMenu {\n    fn render(\n        self,\n        id: impl Into<ElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> impl IntoElement {\n        let id = id.into();\n\n        v_flex()\n            .gap_2()\n            .refine_style(&self.style)\n            .children(self.items.into_iter().enumerate().map(|(ix, item)| {\n                let id = SharedString::from(format!(\"{}-{}\", id, ix));\n                item.collapsed(self.collapsed)\n                    .render(id, window, cx)\n                    .into_any_element()\n            }))\n    }\n}\n\nimpl Styled for SidebarMenu {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\n/// Menu item for the [`SidebarMenu`]\n#[derive(Clone)]\npub struct SidebarMenuItem {\n    icon: Option<Icon>,\n    label: SharedString,\n    handler: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>,\n    active: bool,\n    default_open: bool,\n    click_to_open: bool,\n    collapsed: bool,\n    children: Vec<Self>,\n    suffix: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>,\n    disabled: bool,\n    context_menu: Option<Rc<dyn Fn(PopupMenu, &mut Window, &mut App) -> PopupMenu + 'static>>,\n}\n\nimpl SidebarMenuItem {\n    /// Create a new [`SidebarMenuItem`] with a label.\n    pub fn new(label: impl Into<SharedString>) -> Self {\n        Self {\n            icon: None,\n            label: label.into(),\n            handler: Rc::new(|_, _, _| {}),\n            active: false,\n            collapsed: false,\n            default_open: false,\n            click_to_open: false,\n            children: Vec::new(),\n            suffix: None,\n            disabled: false,\n            context_menu: None,\n        }\n    }\n\n    /// Set the icon for the menu item\n    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {\n        self.icon = Some(icon.into());\n        self\n    }\n\n    /// Set the active state of the menu item\n    pub fn active(mut self, active: bool) -> Self {\n        self.active = active;\n        self\n    }\n\n    /// Add a click handler to the menu item\n    pub fn on_click(\n        mut self,\n        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.handler = Rc::new(handler);\n        self\n    }\n\n    /// Set the collapsed state of the menu item\n    pub fn collapsed(mut self, collapsed: bool) -> Self {\n        self.collapsed = collapsed;\n        self\n    }\n\n    /// Set the default open state of the Submenu, default is `false`.\n    ///\n    /// This only used on initial render, the internal state will be used afterwards.\n    pub fn default_open(mut self, open: bool) -> Self {\n        self.default_open = open;\n        self\n    }\n\n    /// Set whether clicking the menu item open the submenu.\n    ///\n    /// Default is `false`.\n    ///\n    /// If `false` we only handle open/close via the caret button.\n    pub fn click_to_open(mut self, click_to_open: bool) -> Self {\n        self.click_to_open = click_to_open;\n        self\n    }\n\n    pub fn children(mut self, children: impl IntoIterator<Item = impl Into<Self>>) -> Self {\n        self.children = children.into_iter().map(Into::into).collect();\n        self\n    }\n\n    /// Set the suffix for the menu item.\n    pub fn suffix<F, E>(mut self, builder: F) -> Self\n    where\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n        E: IntoElement,\n    {\n        self.suffix = Some(Rc::new(move |window, cx| {\n            builder(window, cx).into_any_element()\n        }));\n        self\n    }\n\n    /// Set disabled flat for menu item.\n    pub fn disable(mut self, disable: bool) -> Self {\n        self.disabled = disable;\n        self\n    }\n\n    fn is_submenu(&self) -> bool {\n        self.children.len() > 0\n    }\n\n    /// Set the context menu for the menu item.\n    pub fn context_menu(\n        mut self,\n        f: impl Fn(PopupMenu, &mut Window, &mut App) -> PopupMenu + 'static,\n    ) -> Self {\n        self.context_menu = Some(Rc::new(f));\n        self\n    }\n}\n\nimpl FluentBuilder for SidebarMenuItem {}\n\nimpl Collapsible for SidebarMenuItem {\n    fn is_collapsed(&self) -> bool {\n        self.collapsed\n    }\n\n    fn collapsed(mut self, collapsed: bool) -> Self {\n        self.collapsed = collapsed;\n        self\n    }\n}\n\nimpl SidebarItem for SidebarMenuItem {\n    fn render(\n        self,\n        id: impl Into<ElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> impl IntoElement {\n        let click_to_open = self.click_to_open;\n        let default_open = self.default_open;\n        let id = id.into();\n        let open_state = window.use_keyed_state(id.clone(), cx, |_, _| default_open);\n        let handler = self.handler.clone();\n        let is_collapsed = self.collapsed;\n        let is_active = self.active;\n        let is_hoverable = !is_active && !self.disabled;\n        let is_disabled = self.disabled;\n        let is_submenu = self.is_submenu();\n        let is_open = is_submenu && !is_collapsed && *open_state.read(cx);\n\n        div()\n            .id(id.clone())\n            .w_full()\n            .child(\n                h_flex()\n                    .size_full()\n                    .id(\"item\")\n                    .overflow_x_hidden()\n                    .flex_shrink_0()\n                    .p_2()\n                    .gap_x_2()\n                    .rounded(cx.theme().radius)\n                    .text_sm()\n                    .when(is_hoverable, |this| {\n                        this.hover(|this| {\n                            this.bg(cx.theme().sidebar_accent.opacity(0.8))\n                                .text_color(cx.theme().sidebar_accent_foreground)\n                        })\n                    })\n                    .when(is_active, |this| {\n                        this.font_medium()\n                            .bg(cx.theme().sidebar_accent)\n                            .text_color(cx.theme().sidebar_accent_foreground)\n                    })\n                    .when_some(self.icon.clone(), |this, icon| this.child(icon))\n                    .when(is_collapsed, |this| {\n                        this.justify_center().when(is_active, |this| {\n                            this.bg(cx.theme().sidebar_accent)\n                                .text_color(cx.theme().sidebar_accent_foreground)\n                        })\n                    })\n                    .when(!is_collapsed, |this| {\n                        this.h_7()\n                            .child(\n                                h_flex()\n                                    .flex_1()\n                                    .gap_x_2()\n                                    .justify_between()\n                                    .overflow_x_hidden()\n                                    .child(\n                                        h_flex()\n                                            .flex_1()\n                                            .overflow_x_hidden()\n                                            .child(self.label.clone()),\n                                    )\n                                    .when_some(self.suffix.clone(), |this, suffix| {\n                                        this.child(suffix(window, cx).into_any_element())\n                                    }),\n                            )\n                            .when(is_submenu, |this| {\n                                this.child(\n                                    Button::new(\"caret\")\n                                        .xsmall()\n                                        .ghost()\n                                        .icon(\n                                            Icon::new(IconName::ChevronRight)\n                                                .size_4()\n                                                .when(is_open, |this| {\n                                                    this.rotate(percentage(90. / 360.))\n                                                }),\n                                        )\n                                        .on_click({\n                                            let open_state = open_state.clone();\n                                            move |_, _, cx| {\n                                                // Avoid trigger item click, just expand/collapse submenu\n                                                cx.stop_propagation();\n                                                open_state.update(cx, |is_open, cx| {\n                                                    *is_open = !*is_open;\n                                                    cx.notify();\n                                                })\n                                            }\n                                        }),\n                                )\n                            })\n                    })\n                    .when(is_disabled, |this| {\n                        this.text_color(cx.theme().muted_foreground)\n                    })\n                    .when(!is_disabled, |this| {\n                        this.on_click({\n                            let open_state = open_state.clone();\n                            move |ev, window, cx| {\n                                if click_to_open {\n                                    open_state.update(cx, |is_open, cx| {\n                                        *is_open = true;\n                                        cx.notify();\n                                    });\n                                }\n\n                                handler(ev, window, cx)\n                            }\n                        })\n                    })\n                    .map(|this| {\n                        if let Some(context_menu) = self.context_menu {\n                            this.context_menu(move |menu, window, cx| {\n                                context_menu(menu, window, cx)\n                            })\n                            .into_any_element()\n                        } else {\n                            this.into_any_element()\n                        }\n                    }),\n            )\n            .when(is_open, |this| {\n                this.child(\n                    v_flex()\n                        .id(\"submenu\")\n                        .border_l_1()\n                        .border_color(cx.theme().sidebar_border)\n                        .gap_1()\n                        .ml_3p5()\n                        .pl_2p5()\n                        .py_0p5()\n                        .children(self.children.into_iter().enumerate().map(|(ix, item)| {\n                            let id = format!(\"{}-{}\", id, ix);\n                            item.render(id, window, cx).into_any_element()\n                        })),\n                )\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/sidebar/mod.rs",
    "content": "use crate::{\n    ActiveTheme, Collapsible, Icon, IconName, Side, Sizable, StyledExt,\n    button::{Button, ButtonVariants},\n    h_flex,\n    scroll::ScrollableElement,\n    v_flex,\n};\nuse gpui::{\n    AnyElement, App, ClickEvent, EdgesRefinement, ElementId, InteractiveElement as _, IntoElement,\n    ListAlignment, ListState, ParentElement, Pixels, RenderOnce, SharedString, StyleRefinement,\n    Styled, Window, div, list, prelude::FluentBuilder, px,\n};\nuse std::rc::Rc;\n\nmod footer;\nmod group;\nmod header;\nmod menu;\npub use footer::*;\npub use group::*;\npub use header::*;\npub use menu::*;\n\nconst DEFAULT_WIDTH: Pixels = px(255.);\nconst COLLAPSED_WIDTH: Pixels = px(48.);\n\npub trait SidebarItem: Collapsible + Clone {\n    fn render(\n        self,\n        id: impl Into<ElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> impl IntoElement;\n}\n\n/// A Sidebar element that can contain collapsible child elements.\n#[derive(IntoElement)]\npub struct Sidebar<E: SidebarItem + 'static> {\n    id: ElementId,\n    style: StyleRefinement,\n    content: Vec<E>,\n    /// header view\n    header: Option<AnyElement>,\n    /// footer view\n    footer: Option<AnyElement>,\n    /// The side of the sidebar\n    side: Side,\n    collapsible: bool,\n    collapsed: bool,\n}\n\nimpl<E: SidebarItem> Sidebar<E> {\n    /// Create a new Sidebar with the given ID.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default(),\n            content: vec![],\n            header: None,\n            footer: None,\n            side: Side::Left,\n            collapsible: true,\n            collapsed: false,\n        }\n    }\n\n    /// Set the side of the sidebar.\n    ///\n    /// Default is `Side::Left`.\n    pub fn side(mut self, side: Side) -> Self {\n        self.side = side;\n        self\n    }\n\n    /// Set the sidebar to be collapsible, default is true\n    pub fn collapsible(mut self, collapsible: bool) -> Self {\n        self.collapsible = collapsible;\n        self\n    }\n\n    /// Set the sidebar to be collapsed\n    pub fn collapsed(mut self, collapsed: bool) -> Self {\n        self.collapsed = collapsed;\n        self\n    }\n\n    /// Set the header of the sidebar.\n    pub fn header(mut self, header: impl IntoElement) -> Self {\n        self.header = Some(header.into_any_element());\n        self\n    }\n\n    /// Set the footer of the sidebar.\n    pub fn footer(mut self, footer: impl IntoElement) -> Self {\n        self.footer = Some(footer.into_any_element());\n        self\n    }\n\n    /// Add a child element to the sidebar, the child must implement `Collapsible`\n    pub fn child(mut self, child: E) -> Self {\n        self.content.push(child);\n        self\n    }\n\n    /// Add multiple children to the sidebar, the children must implement `Collapsible`\n    pub fn children(mut self, children: impl IntoIterator<Item = E>) -> Self {\n        self.content.extend(children);\n        self\n    }\n}\n\n/// Toggle button to collapse/expand the [`Sidebar`].\n#[derive(IntoElement)]\npub struct SidebarToggleButton {\n    btn: Button,\n    collapsed: bool,\n    side: Side,\n    on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,\n}\n\nimpl SidebarToggleButton {\n    /// Create a new SidebarToggleButton.\n    pub fn new() -> Self {\n        Self {\n            btn: Button::new(\"collapse\").ghost().small(),\n            collapsed: false,\n            side: Side::Left,\n            on_click: None,\n        }\n    }\n\n    /// Set the side of the toggle button.\n    ///\n    /// Default is `Side::Left`.\n    pub fn side(mut self, side: Side) -> Self {\n        self.side = side;\n        self\n    }\n\n    /// Set the collapsed state of the toggle button.\n    pub fn collapsed(mut self, collapsed: bool) -> Self {\n        self.collapsed = collapsed;\n        self\n    }\n\n    /// Add a click handler to the toggle button.\n    pub fn on_click(\n        mut self,\n        on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_click = Some(Rc::new(on_click));\n        self\n    }\n}\n\nimpl RenderOnce for SidebarToggleButton {\n    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {\n        let collapsed = self.collapsed;\n        let on_click = self.on_click.clone();\n\n        let icon = if collapsed {\n            if self.side.is_left() {\n                IconName::PanelLeftOpen\n            } else {\n                IconName::PanelRightOpen\n            }\n        } else {\n            if self.side.is_left() {\n                IconName::PanelLeftClose\n            } else {\n                IconName::PanelRightClose\n            }\n        };\n\n        self.btn\n            .when_some(on_click, |this, on_click| {\n                this.on_click(move |ev, window, cx| {\n                    on_click(ev, window, cx);\n                })\n            })\n            .icon(Icon::new(icon).size_4())\n    }\n}\n\nimpl<E: SidebarItem> Styled for Sidebar<E> {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl<E: SidebarItem> RenderOnce for Sidebar<E> {\n    fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        self.style.padding = EdgesRefinement::default();\n\n        let content_len = self.content.len();\n        let overdraw = px(window.viewport_size().height.as_f32() * 0.3);\n        let list_state = window\n            .use_keyed_state(\n                SharedString::from(format!(\"{}-list-state\", self.id)),\n                cx,\n                |_, _| ListState::new(content_len, ListAlignment::Top, overdraw),\n            )\n            .read(cx)\n            .clone();\n        if list_state.item_count() != content_len {\n            list_state.reset(content_len);\n        }\n\n        v_flex()\n            .id(self.id)\n            .w(DEFAULT_WIDTH)\n            .flex_shrink_0()\n            .h_full()\n            .overflow_hidden()\n            .relative()\n            .bg(cx.theme().sidebar)\n            .text_color(cx.theme().sidebar_foreground)\n            .border_color(cx.theme().sidebar_border)\n            .map(|this| match self.side {\n                Side::Left => this.border_r_1(),\n                Side::Right => this.border_l_1(),\n            })\n            .refine_style(&self.style)\n            .when(self.collapsed, |this| this.w(COLLAPSED_WIDTH).gap_2())\n            .when_some(self.header.take(), |this, header| {\n                this.child(\n                    h_flex()\n                        .id(\"header\")\n                        .pt_3()\n                        .px_3()\n                        .gap_2()\n                        .when(self.collapsed, |this| this.pt_2().px_2())\n                        .child(header),\n                )\n            })\n            .child(\n                v_flex().id(\"content\").flex_1().min_h_0().child(\n                    v_flex()\n                        .id(\"inner\")\n                        .size_full()\n                        .px_3()\n                        .gap_y_3()\n                        .when(self.collapsed, |this| this.p_2())\n                        .child(\n                            list(list_state.clone(), {\n                                move |ix, window, cx| {\n                                    let group = self.content.get(ix).cloned();\n                                    let is_first = ix == 0;\n                                    let is_last =\n                                        content_len > 0 && ix == content_len.saturating_sub(1);\n                                    div()\n                                        .id(ix)\n                                        .when_some(group, |this, group| {\n                                            this.child(\n                                                group\n                                                    .collapsed(self.collapsed)\n                                                    .render(ix, window, cx)\n                                                    .into_any_element(),\n                                            )\n                                        })\n                                        .when(is_first, |this| this.pt_3())\n                                        .when(is_last, |this| this.pb_3())\n                                        .into_any_element()\n                                }\n                            })\n                            .size_full(),\n                        )\n                        .vertical_scrollbar(&list_state),\n                ),\n            )\n            .when_some(self.footer.take(), |this, footer| {\n                this.child(\n                    h_flex()\n                        .id(\"footer\")\n                        .pb_3()\n                        .px_3()\n                        .gap_2()\n                        .when(self.collapsed, |this| this.pt_2().px_2())\n                        .child(footer),\n                )\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/skeleton.rs",
    "content": "use crate::{ActiveTheme, StyledExt};\nuse gpui::{\n    bounce, div, ease_in_out, Animation, AnimationExt, IntoElement, RenderOnce, StyleRefinement,\n    Styled,\n};\nuse instant::Duration;\n\n/// A skeleton loading placeholder element.\n#[derive(IntoElement)]\npub struct Skeleton {\n    style: StyleRefinement,\n    secondary: bool,\n}\n\nimpl Skeleton {\n    /// Create a new Skeleton element.\n    pub fn new() -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            secondary: false,\n        }\n    }\n\n    /// Set use secondary color.\n    pub fn secondary(mut self) -> Self {\n        self.secondary = true;\n        self\n    }\n}\n\nimpl Styled for Skeleton {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Skeleton {\n    fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {\n        div()\n            .w_full()\n            .h_4()\n            .bg(if self.secondary {\n                cx.theme().skeleton.opacity(0.5)\n            } else {\n                cx.theme().skeleton\n            })\n            .refine_style(&self.style)\n            .with_animation(\n                \"skeleton\",\n                Animation::new(Duration::from_secs(2))\n                    .repeat()\n                    .with_easing(bounce(ease_in_out)),\n                move |this, delta| {\n                    let v = 1.0 - delta * 0.5;\n                    this.opacity(v)\n                },\n            )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/slider.rs",
    "content": "use std::ops::Range;\n\nuse crate::{ActiveTheme, AxisExt, ElementExt, StyledExt, h_flex};\nuse gpui::{\n    Along, App, AppContext as _, Axis, Background, Bounds, Context, Corners, DefiniteLength,\n    DragMoveEvent, Empty, Entity, EntityId, EventEmitter, Hsla, InteractiveElement, IntoElement,\n    IsZero, MouseButton, MouseDownEvent, ParentElement as _, Pixels, Point, Render, RenderOnce,\n    StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div,\n    prelude::FluentBuilder as _, px, relative,\n};\n\n#[derive(Clone)]\nstruct DragThumb((EntityId, bool));\n\nimpl Render for DragThumb {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        Empty\n    }\n}\n\n#[derive(Clone)]\nstruct DragSlider(EntityId);\n\nimpl Render for DragSlider {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        Empty\n    }\n}\n\n/// Events emitted by the [`SliderState`].\npub enum SliderEvent {\n    Change(SliderValue),\n}\n\n/// The value of the slider, can be a single value or a range of values.\n///\n/// - Can from a f32 value, which will be treated as a single value.\n/// - Or from a (f32, f32) tuple, which will be treated as a range of values.\n///\n/// The default value is `SliderValue::Single(0.0)`.\n#[derive(Clone, Copy, Debug, PartialEq)]\npub enum SliderValue {\n    Single(f32),\n    Range(f32, f32),\n}\n\nimpl std::fmt::Display for SliderValue {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            SliderValue::Single(value) => write!(f, \"{}\", value),\n            SliderValue::Range(start, end) => write!(f, \"{}..{}\", start, end),\n        }\n    }\n}\n\nimpl From<f32> for SliderValue {\n    fn from(value: f32) -> Self {\n        SliderValue::Single(value)\n    }\n}\n\nimpl From<(f32, f32)> for SliderValue {\n    fn from(value: (f32, f32)) -> Self {\n        SliderValue::Range(value.0, value.1)\n    }\n}\n\nimpl From<Range<f32>> for SliderValue {\n    fn from(value: Range<f32>) -> Self {\n        SliderValue::Range(value.start, value.end)\n    }\n}\n\nimpl Default for SliderValue {\n    fn default() -> Self {\n        SliderValue::Single(0.)\n    }\n}\n\nimpl SliderValue {\n    /// Clamp the value to the given range.\n    pub fn clamp(self, min: f32, max: f32) -> Self {\n        match self {\n            SliderValue::Single(value) => SliderValue::Single(value.clamp(min, max)),\n            SliderValue::Range(start, end) => {\n                SliderValue::Range(start.clamp(min, max), end.clamp(min, max))\n            }\n        }\n    }\n\n    /// Check if the value is a single value.\n    #[inline]\n    pub fn is_single(&self) -> bool {\n        matches!(self, SliderValue::Single(_))\n    }\n\n    /// Check if the value is a range of values.\n    #[inline]\n    pub fn is_range(&self) -> bool {\n        matches!(self, SliderValue::Range(_, _))\n    }\n\n    /// Get the start value.\n    pub fn start(&self) -> f32 {\n        match self {\n            SliderValue::Single(value) => *value,\n            SliderValue::Range(start, _) => *start,\n        }\n    }\n\n    /// Get the end value.\n    pub fn end(&self) -> f32 {\n        match self {\n            SliderValue::Single(value) => *value,\n            SliderValue::Range(_, end) => *end,\n        }\n    }\n\n    fn set_start(&mut self, value: f32) {\n        if let SliderValue::Range(_, end) = self {\n            *self = SliderValue::Range(value.min(*end), *end);\n        } else {\n            *self = SliderValue::Single(value);\n        }\n    }\n\n    fn set_end(&mut self, value: f32) {\n        if let SliderValue::Range(start, _) = self {\n            *self = SliderValue::Range(*start, value.max(*start));\n        } else {\n            *self = SliderValue::Single(value);\n        }\n    }\n}\n\n/// The scale mode of the slider.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\npub enum SliderScale {\n    /// Linear scale where values change uniformly across the slider range.\n    /// This is the default mode.\n    #[default]\n    Linear,\n    /// Logarithmic scale where the distance between values increases exponentially.\n    ///\n    /// This is useful for parameters that have a large range of values where smaller\n    /// changes are more significant at lower values. Common examples include:\n    ///\n    /// - Volume controls (human hearing perception is logarithmic)\n    /// - Frequency controls (musical notes follow a logarithmic scale)\n    /// - Zoom levels\n    /// - Any parameter where you want finer control at lower values\n    ///\n    /// # For example\n    ///\n    /// ```\n    /// use gpui_component::slider::{SliderState, SliderScale};\n    ///\n    /// let slider = SliderState::new()\n    ///     .min(1.0)    // Must be > 0 for logarithmic scale\n    ///     .max(1000.0)\n    ///     .scale(SliderScale::Logarithmic);\n    /// ```\n    ///\n    /// - Moving the slider 1/3 of the way will yield ~10\n    /// - Moving it 2/3 of the way will yield ~100\n    /// - The full range covers 3 orders of magnitude evenly\n    Logarithmic,\n}\n\nimpl SliderScale {\n    #[inline]\n    pub fn is_linear(&self) -> bool {\n        matches!(self, SliderScale::Linear)\n    }\n\n    #[inline]\n    pub fn is_logarithmic(&self) -> bool {\n        matches!(self, SliderScale::Logarithmic)\n    }\n}\n\n/// State of the [`Slider`].\npub struct SliderState {\n    min: f32,\n    max: f32,\n    step: f32,\n    value: SliderValue,\n    /// When is single value mode, only `end` is used, the start is always 0.0.\n    percentage: Range<f32>,\n    /// The bounds of the slider after rendered.\n    bounds: Bounds<Pixels>,\n    scale: SliderScale,\n}\n\nimpl SliderState {\n    /// Create a new [`SliderState`].\n    pub fn new() -> Self {\n        Self {\n            min: 0.0,\n            max: 100.0,\n            step: 1.0,\n            value: SliderValue::default(),\n            percentage: (0.0..0.0),\n            bounds: Bounds::default(),\n            scale: SliderScale::default(),\n        }\n    }\n\n    /// Set the minimum value of the slider, default: 0.0\n    pub fn min(mut self, min: f32) -> Self {\n        if self.scale.is_logarithmic() {\n            assert!(\n                min > 0.0,\n                \"`min` must be greater than 0 for SliderScale::Logarithmic\"\n            );\n            assert!(\n                min < self.max,\n                \"`min` must be less than `max` for Logarithmic scale\"\n            );\n        }\n        self.min = min;\n        self.update_thumb_pos();\n        self\n    }\n\n    /// Set the maximum value of the slider, default: 100.0\n    pub fn max(mut self, max: f32) -> Self {\n        if self.scale.is_logarithmic() {\n            assert!(\n                max > self.min,\n                \"`max` must be greater than `min` for Logarithmic scale\"\n            );\n        }\n        self.max = max;\n        self.update_thumb_pos();\n        self\n    }\n\n    /// Set the step value of the slider, default: 1.0\n    pub fn step(mut self, step: f32) -> Self {\n        self.step = step;\n        self\n    }\n\n    /// Set the scale of the slider, default: [`SliderScale::Linear`].\n    pub fn scale(mut self, scale: SliderScale) -> Self {\n        if scale.is_logarithmic() {\n            assert!(\n                self.min > 0.0,\n                \"`min` must be greater than 0 for Logarithmic scale\"\n            );\n            assert!(\n                self.max > self.min,\n                \"`max` must be greater than `min` for Logarithmic scale\"\n            );\n        }\n        self.scale = scale;\n        self.update_thumb_pos();\n        self\n    }\n\n    /// Set the default value of the slider, default: 0.0\n    pub fn default_value(mut self, value: impl Into<SliderValue>) -> Self {\n        self.value = value.into();\n        self.update_thumb_pos();\n        self\n    }\n\n    /// Set the value of the slider.\n    pub fn set_value(\n        &mut self,\n        value: impl Into<SliderValue>,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.value = value.into();\n        self.update_thumb_pos();\n        cx.notify();\n    }\n\n    /// Get the value of the slider.\n    pub fn value(&self) -> SliderValue {\n        self.value\n    }\n\n    /// Converts a value between 0.0 and 1.0 to a value between the minimum and maximum value,\n    /// depending on the chosen scale.\n    fn percentage_to_value(&self, percentage: f32) -> f32 {\n        match self.scale {\n            SliderScale::Linear => self.min + (self.max - self.min) * percentage,\n            SliderScale::Logarithmic => {\n                // when percentage is 0, this simplifies to (max/min)^0 * min = 1 * min = min\n                // when percentage is 1, this simplifies to (max/min)^1 * min = (max*min)/min = max\n                // we clamp just to make sure we don't have issue with floating point precision\n                let base = self.max / self.min;\n                (base.powf(percentage) * self.min).clamp(self.min, self.max)\n            }\n        }\n    }\n\n    /// Converts a value between the minimum and maximum value to a value between 0.0 and 1.0,\n    /// depending on the chosen scale.\n    fn value_to_percentage(&self, value: f32) -> f32 {\n        match self.scale {\n            SliderScale::Linear => {\n                let range = self.max - self.min;\n                if range <= 0.0 {\n                    0.0\n                } else {\n                    (value - self.min) / range\n                }\n            }\n            SliderScale::Logarithmic => {\n                let base = self.max / self.min;\n                (value / self.min).log(base).clamp(0.0, 1.0)\n            }\n        }\n    }\n\n    fn update_thumb_pos(&mut self) {\n        match self.value {\n            SliderValue::Single(value) => {\n                let percentage = self.value_to_percentage(value.clamp(self.min, self.max));\n                self.percentage = 0.0..percentage;\n            }\n            SliderValue::Range(start, end) => {\n                let clamped_start = start.clamp(self.min, self.max);\n                let clamped_end = end.clamp(self.min, self.max);\n                self.percentage =\n                    self.value_to_percentage(clamped_start)..self.value_to_percentage(clamped_end);\n            }\n        }\n    }\n\n    /// Update value by mouse position\n    fn update_value_by_position(\n        &mut self,\n        axis: Axis,\n        position: Point<Pixels>,\n        is_start: bool,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let bounds = self.bounds;\n        let step = self.step;\n\n        let inner_pos = if axis.is_horizontal() {\n            position.x - bounds.left()\n        } else {\n            bounds.bottom() - position.y\n        };\n        let total_size = bounds.size.along(axis);\n        let percentage = inner_pos.clamp(px(0.), total_size) / total_size;\n\n        let percentage = if is_start {\n            percentage.clamp(0.0, self.percentage.end)\n        } else {\n            percentage.clamp(self.percentage.start, 1.0)\n        };\n\n        let value = self.percentage_to_value(percentage);\n        let value = (value / step).round() * step;\n\n        if is_start {\n            self.percentage.start = percentage;\n            self.value.set_start(value);\n        } else {\n            self.percentage.end = percentage;\n            self.value.set_end(value);\n        }\n        cx.emit(SliderEvent::Change(self.value));\n        cx.notify();\n    }\n}\n\nimpl EventEmitter<SliderEvent> for SliderState {}\n\n/// A Slider element.\n#[derive(IntoElement)]\npub struct Slider {\n    state: Entity<SliderState>,\n    axis: Axis,\n    style: StyleRefinement,\n    disabled: bool,\n}\n\nimpl Slider {\n    /// Create a new [`Slider`] element bind to the [`SliderState`].\n    pub fn new(state: &Entity<SliderState>) -> Self {\n        Self {\n            axis: Axis::Horizontal,\n            state: state.clone(),\n            style: StyleRefinement::default(),\n            disabled: false,\n        }\n    }\n\n    /// As a horizontal slider.\n    pub fn horizontal(mut self) -> Self {\n        self.axis = Axis::Horizontal;\n        self\n    }\n\n    /// As a vertical slider.\n    pub fn vertical(mut self) -> Self {\n        self.axis = Axis::Vertical;\n        self\n    }\n\n    /// Set the disabled state of the slider, default: false\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    fn render_thumb(\n        &self,\n        start: DefiniteLength,\n        is_start: bool,\n        bar_color: Background,\n        thumb_color: Hsla,\n        radius: Corners<Pixels>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> impl gpui::IntoElement {\n        let entity_id = self.state.entity_id();\n        let axis = self.axis;\n        let id = (\"slider-thumb\", is_start as u32);\n\n        if self.disabled {\n            return div().id(id);\n        }\n\n        div()\n            .id(id)\n            .absolute()\n            .when(axis.is_horizontal(), |this| {\n                this.top(px(-5.)).left(start).ml(-px(8.))\n            })\n            .when(axis.is_vertical(), |this| {\n                this.bottom(start).left(px(-5.)).mb(-px(8.))\n            })\n            .flex()\n            .items_center()\n            .justify_center()\n            .flex_shrink_0()\n            .corner_radii(radius)\n            .bg(bar_color.opacity(0.5))\n            .when(cx.theme().shadow, |this| this.shadow_md())\n            .size_4()\n            .p(px(1.))\n            .child(\n                div()\n                    .flex_shrink_0()\n                    .size_full()\n                    .corner_radii(radius)\n                    .bg(thumb_color),\n            )\n            .on_mouse_down(MouseButton::Left, |_, _, cx| {\n                cx.stop_propagation();\n            })\n            .on_drag(DragThumb((entity_id, is_start)), |drag, _, _, cx| {\n                cx.stop_propagation();\n                cx.new(|_| drag.clone())\n            })\n            .on_drag_move(window.listener_for(\n                &self.state,\n                move |view, e: &DragMoveEvent<DragThumb>, window, cx| {\n                    match e.drag(cx) {\n                        DragThumb((id, is_start)) => {\n                            if *id != entity_id {\n                                return;\n                            }\n\n                            // set value by mouse position\n                            view.update_value_by_position(\n                                axis,\n                                e.event.position,\n                                *is_start,\n                                window,\n                                cx,\n                            )\n                        }\n                    }\n                },\n            ))\n    }\n}\n\nimpl Styled for Slider {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Slider {\n    fn render(self, window: &mut Window, cx: &mut gpui::App) -> impl IntoElement {\n        let axis = self.axis;\n        let entity_id = self.state.entity_id();\n        let state = self.state.read(cx);\n        let is_range = state.value().is_range();\n        let percentage = state.percentage.clone();\n        let bar_start = relative(percentage.start);\n        let bar_end = relative(1. - percentage.end);\n        let rem_size = window.rem_size();\n\n        let bar_color = self\n            .style\n            .background\n            .clone()\n            .and_then(|bg| bg.color())\n            .unwrap_or(cx.theme().slider_bar.into());\n        let thumb_color = self\n            .style\n            .text\n            .color\n            .unwrap_or_else(|| cx.theme().slider_thumb);\n        let corner_radii = self.style.corner_radii.clone();\n        let default_radius = px(999.);\n        let mut radius = Corners {\n            top_left: corner_radii\n                .top_left\n                .map(|v| v.to_pixels(rem_size))\n                .unwrap_or(default_radius),\n            top_right: corner_radii\n                .top_right\n                .map(|v| v.to_pixels(rem_size))\n                .unwrap_or(default_radius),\n            bottom_left: corner_radii\n                .bottom_left\n                .map(|v| v.to_pixels(rem_size))\n                .unwrap_or(default_radius),\n            bottom_right: corner_radii\n                .bottom_right\n                .map(|v| v.to_pixels(rem_size))\n                .unwrap_or(default_radius),\n        };\n        if cx.theme().radius.is_zero() {\n            radius.top_left = px(0.);\n            radius.top_right = px(0.);\n            radius.bottom_left = px(0.);\n            radius.bottom_right = px(0.);\n        }\n\n        div()\n            .id((\"slider\", self.state.entity_id()))\n            .flex()\n            .flex_1()\n            .items_center()\n            .justify_center()\n            .when(axis.is_vertical(), |this| this.h(px(120.)))\n            .when(axis.is_horizontal(), |this| this.w_full())\n            .refine_style(&self.style)\n            .bg(cx.theme().transparent)\n            .text_color(cx.theme().foreground)\n            .child(\n                h_flex()\n                    .id(\"slider-bar-container\")\n                    .when(!self.disabled, |this| {\n                        this.on_mouse_down(\n                            MouseButton::Left,\n                            window.listener_for(\n                                &self.state,\n                                move |state, e: &MouseDownEvent, window, cx| {\n                                    let mut is_start = false;\n                                    if is_range {\n                                        let bar_size = state.bounds.size.along(axis);\n                                        let inner_pos = if axis.is_horizontal() {\n                                            e.position.x - state.bounds.left()\n                                        } else {\n                                            state.bounds.bottom() - e.position.y\n                                        };\n                                        let center = ((percentage.end - percentage.start) / 2.0\n                                            + percentage.start)\n                                            * bar_size;\n                                        is_start = inner_pos < center;\n                                    }\n\n                                    state.update_value_by_position(\n                                        axis, e.position, is_start, window, cx,\n                                    )\n                                },\n                            ),\n                        )\n                    })\n                    .when(!self.disabled && !is_range, |this| {\n                        this.on_drag(DragSlider(entity_id), |drag, _, _, cx| {\n                            cx.stop_propagation();\n                            cx.new(|_| drag.clone())\n                        })\n                        .on_drag_move(window.listener_for(\n                            &self.state,\n                            move |view, e: &DragMoveEvent<DragSlider>, window, cx| match e.drag(cx)\n                            {\n                                DragSlider(id) => {\n                                    if *id != entity_id {\n                                        return;\n                                    }\n\n                                    view.update_value_by_position(\n                                        axis,\n                                        e.event.position,\n                                        false,\n                                        window,\n                                        cx,\n                                    )\n                                }\n                            },\n                        ))\n                    })\n                    .when(axis.is_horizontal(), |this| {\n                        this.items_center().h_6().w_full()\n                    })\n                    .when(axis.is_vertical(), |this| {\n                        this.justify_center().w_6().h_full()\n                    })\n                    .flex_shrink_0()\n                    .child(\n                        div()\n                            .id(\"slider-bar\")\n                            .relative()\n                            .when(axis.is_horizontal(), |this| this.w_full().h_1p5())\n                            .when(axis.is_vertical(), |this| this.h_full().w_1p5())\n                            .bg(bar_color.opacity(0.2))\n                            .active(|this| this.bg(bar_color.opacity(0.4)))\n                            .corner_radii(radius)\n                            .child(\n                                div()\n                                    .absolute()\n                                    .when(axis.is_horizontal(), |this| {\n                                        this.h_full().left(bar_start).right(bar_end)\n                                    })\n                                    .when(axis.is_vertical(), |this| {\n                                        this.w_full().bottom(bar_start).top(bar_end)\n                                    })\n                                    .bg(bar_color)\n                                    .when(!cx.theme().radius.is_zero(), |this| this.rounded_full()),\n                            )\n                            .when(is_range, |this| {\n                                this.child(self.render_thumb(\n                                    relative(percentage.start),\n                                    true,\n                                    bar_color,\n                                    thumb_color,\n                                    radius,\n                                    window,\n                                    cx,\n                                ))\n                            })\n                            .child(self.render_thumb(\n                                relative(percentage.end),\n                                false,\n                                bar_color,\n                                thumb_color,\n                                radius,\n                                window,\n                                cx,\n                            ))\n                            .on_prepaint({\n                                let state = self.state.clone();\n                                move |bounds, _, cx| state.update(cx, |r, _| r.bounds = bounds)\n                            }),\n                    ),\n            )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/spinner.rs",
    "content": "use instant::Duration;\nuse crate::{Icon, IconName, Sizable, Size};\nuse gpui::{\n    div, ease_in_out, percentage, prelude::FluentBuilder as _, Animation, AnimationExt as _, App,\n    Hsla, IntoElement, ParentElement, RenderOnce, Styled as _, Transformation, Window,\n};\n\n/// A cycling loading spinner.\n#[derive(IntoElement)]\npub struct Spinner {\n    size: Size,\n    icon: Icon,\n    speed: Duration,\n    color: Option<Hsla>,\n}\n\nimpl Spinner {\n    /// Create a new loading spinner.\n    pub fn new() -> Self {\n        Self {\n            size: Size::Medium,\n            speed: Duration::from_secs_f64(0.8),\n            icon: Icon::new(IconName::Loader),\n            color: None,\n        }\n    }\n\n    /// Set specified icon for the spinner.\n    ///\n    /// Default is [`IconName::Loader`].\n    ///\n    /// Please ensure the icon used is suitable for a loading spinner.\n    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {\n        self.icon = icon.into();\n        self\n    }\n\n    /// Set the icon color.\n    pub fn color(mut self, color: Hsla) -> Self {\n        self.color = Some(color);\n        self\n    }\n}\n\nimpl Sizable for Spinner {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl RenderOnce for Spinner {\n    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {\n        div()\n            .child(\n                self.icon\n                    .with_size(self.size)\n                    .when_some(self.color, |this, color| this.text_color(color))\n                    .with_animation(\n                        \"circle\",\n                        Animation::new(self.speed).repeat().with_easing(ease_in_out),\n                        |this, delta| this.transform(Transformation::rotate(percentage(delta))),\n                    ),\n            )\n            .into_element()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/stepper/item.rs",
    "content": "use gpui::{\n    AnyElement, App, Axis, ClickEvent, Half, InteractiveElement as _, IntoElement, ParentElement,\n    Pixels, RenderOnce, StyleRefinement, Styled, Window, div, prelude::FluentBuilder as _, px,\n    relative,\n};\n\nuse crate::{\n    ActiveTheme as _, AxisExt, Icon, Sizable, Size, StyledExt as _,\n    stepper::trigger::StepperTrigger,\n};\n\n/// A step item within a [`Stepper`].\n#[derive(IntoElement)]\npub struct StepperItem {\n    step: usize,\n    checked_step: usize,\n    style: StyleRefinement,\n    icon: Option<Icon>,\n    children: Vec<AnyElement>,\n    layout: Axis,\n    disabled: bool,\n    size: Size,\n    is_last: bool,\n    text_center: bool,\n    on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,\n}\n\nimpl StepperItem {\n    pub fn new() -> Self {\n        Self {\n            step: 0,\n            checked_step: 0,\n            style: StyleRefinement::default(),\n            icon: None,\n            layout: Axis::Horizontal,\n            disabled: false,\n            size: Size::default(),\n            is_last: false,\n            text_center: false,\n            children: Vec::new(),\n            on_click: Box::new(|_, _, _| {}),\n        }\n    }\n\n    /// Set the icon of the stepper item.\n    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {\n        self.icon = Some(icon.into());\n        self\n    }\n\n    /// Set disabled state of the stepper item.\n    ///\n    /// Will override the stepper's disabled state if set to true.\n    ///\n    /// Default is false.\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    pub(super) fn text_center(mut self, center: bool) -> Self {\n        self.text_center = center;\n        self\n    }\n\n    pub(super) fn step(mut self, ix: usize) -> Self {\n        self.step = ix;\n        self\n    }\n\n    pub(super) fn checked_step(mut self, checked_step: usize) -> Self {\n        self.checked_step = checked_step;\n        self\n    }\n\n    pub(super) fn layout(mut self, layout: Axis) -> Self {\n        self.layout = layout;\n        self\n    }\n\n    pub(super) fn is_last(mut self, is_last: bool) -> Self {\n        self.is_last = is_last;\n        self\n    }\n\n    pub(super) fn on_click<F>(mut self, f: F) -> Self\n    where\n        F: Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    {\n        self.on_click = Box::new(f);\n        self\n    }\n}\n\nimpl ParentElement for StepperItem {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Sizable for StepperItem {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Styled for StepperItem {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for StepperItem {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        let is_passed = self.step < self.checked_step;\n        let icon_size = match self.size {\n            Size::XSmall => px(8.),\n            Size::Small => px(18.),\n            Size::Large => px(32.),\n            _ => px(24.),\n        };\n\n        div()\n            .id((\"stepper-item\", self.step))\n            .relative()\n            .when(self.layout.is_horizontal(), |this| this.h_flex())\n            .when(self.layout.is_vertical(), |this| this.v_flex())\n            .when(!self.is_last, |this| this.flex_1())\n            .when(self.text_center, |this| this.flex_1().justify_center())\n            .items_start()\n            .refine_style(&self.style)\n            .child(\n                StepperTrigger::new()\n                    .icon(self.icon)\n                    .icon_size(icon_size)\n                    .step(self.step)\n                    .with_size(self.size)\n                    .checked_step(self.checked_step)\n                    .text_center(self.text_center)\n                    .layout(self.layout)\n                    .disabled(self.disabled)\n                    .children(self.children)\n                    .on_click({\n                        let on_click = self.on_click;\n                        move |e, window, cx| {\n                            on_click(e, window, cx);\n                        }\n                    }),\n            )\n            .when(!self.is_last, |this| {\n                this.child(\n                    StepperSeparator::new()\n                        .with_size(self.size)\n                        .layout(self.layout)\n                        .text_center(self.text_center)\n                        .icon_size(icon_size)\n                        .checked(is_passed),\n                )\n            })\n    }\n}\n\n/// A separator between stepper items.\n///\n/// Default is `absolute` positioned.\n#[derive(IntoElement)]\nstruct StepperSeparator {\n    size: Size,\n    checked: bool,\n    icon_size: Pixels,\n    layout: Axis,\n    style: StyleRefinement,\n    text_center: bool,\n}\n\nimpl StepperSeparator {\n    fn new() -> Self {\n        Self {\n            size: Size::default(),\n            checked: false,\n            icon_size: px(24.),\n            layout: Axis::Horizontal,\n            style: StyleRefinement::default(),\n            text_center: false,\n        }\n    }\n\n    fn with_size(mut self, size: Size) -> Self {\n        self.size = size;\n        self\n    }\n\n    fn text_center(mut self, center: bool) -> Self {\n        self.text_center = center;\n        self\n    }\n\n    fn layout(mut self, layout: Axis) -> Self {\n        self.layout = layout;\n        self\n    }\n\n    fn icon_size(mut self, size: Pixels) -> Self {\n        self.icon_size = size;\n        self\n    }\n\n    fn checked(mut self, checked: bool) -> Self {\n        self.checked = checked;\n        self\n    }\n}\n\nimpl Styled for StepperSeparator {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for StepperSeparator {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let icon_size = self.icon_size;\n        let text_center = self.text_center;\n        let separator_wide = match self.size {\n            Size::XSmall => px(1.5),\n            Size::Large => px(3.),\n            _ => px(2.),\n        };\n\n        let gap = px(4.);\n\n        div()\n            .absolute()\n            .flex_1()\n            .when(self.layout.is_horizontal(), |this| {\n                this.h(separator_wide).mt(icon_size.half()).map(|this| {\n                    if !text_center {\n                        this.ml(icon_size + gap).mr(gap).left_0().right_0()\n                    } else {\n                        this.mx(icon_size.half() + gap)\n                            .left(relative(0.5))\n                            .right(relative(-0.5))\n                    }\n                })\n            })\n            .when(self.layout.is_vertical(), |this| {\n                this.w(separator_wide).ml(icon_size.half()).map(|this| {\n                    if !text_center {\n                        this.mt(icon_size + gap).mb(gap).top_0().bottom_0()\n                    } else {\n                        this.mx(icon_size.half() + gap)\n                            .top(relative(0.5))\n                            .bottom(relative(-0.5))\n                    }\n                })\n            })\n            .refine_style(&self.style)\n            .bg(cx.theme().border)\n            .when(self.checked, |this| this.bg(cx.theme().primary))\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/stepper/mod.rs",
    "content": "mod item;\nmod stepper;\npub(super) mod trigger;\n\npub use item::*;\npub use stepper::*;\n"
  },
  {
    "path": "crates/ui/src/stepper/stepper.rs",
    "content": "use std::rc::Rc;\n\nuse gpui::{\n    App, Axis, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,\n    StyleRefinement, Styled, Window, div, prelude::FluentBuilder as _,\n};\n\nuse crate::{AxisExt, Sizable, Size, StyledExt as _, stepper::StepperItem};\n\n/// A step-by-step progress for users to navigate through a series of steps or stages.\n#[derive(IntoElement)]\npub struct Stepper {\n    id: ElementId,\n    style: StyleRefinement,\n    items: Vec<StepperItem>,\n    step: usize,\n    layout: Axis,\n    disabled: bool,\n    size: Size,\n    text_center: bool,\n    on_click: Rc<dyn Fn(&usize, &mut Window, &mut App) + 'static>,\n}\n\nimpl Stepper {\n    /// Creates a new stepper with the given ID.\n    ///\n    /// Default use is horizontal layout with step 0 selected.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            id: id.into(),\n            style: StyleRefinement::default(),\n            items: Vec::new(),\n            step: 0,\n            layout: Axis::Horizontal,\n            disabled: false,\n            size: Size::default(),\n            text_center: false,\n            on_click: Rc::new(|_, _, _| {}),\n        }\n    }\n\n    /// Set whether to center the text within each stepper item.\n    pub fn text_center(mut self, center: bool) -> Self {\n        self.text_center = center;\n        self\n    }\n\n    /// Set the layout of the stepper, default is horizontal.\n    pub fn layout(mut self, layout: Axis) -> Self {\n        self.layout = layout;\n        self\n    }\n\n    /// Sets the layout of the stepper to Vertical.\n    pub fn vertical(mut self) -> Self {\n        self.layout = Axis::Vertical;\n        self\n    }\n\n    /// Sets the selected index of the stepper, default is 0.\n    pub fn selected_index(mut self, index: usize) -> Self {\n        self.step = index;\n        self\n    }\n\n    /// Adds a stepper item to the stepper.\n    pub fn item(mut self, item: StepperItem) -> Self {\n        self.items.push(item);\n        self\n    }\n\n    /// Add multiple stepper items to the stepper.\n    pub fn items(mut self, items: impl IntoIterator<Item = StepperItem>) -> Self {\n        self.items.extend(items);\n        self\n    }\n\n    /// Set the disabled state of the stepper, default is false.\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    /// Add an on_click handler for when a step is clicked.\n    ///\n    /// The first parameter is the `step` of currently clicked item.\n    pub fn on_click<F>(mut self, f: F) -> Self\n    where\n        F: Fn(&usize, &mut Window, &mut App) + 'static,\n    {\n        self.on_click = Rc::new(f);\n        self\n    }\n}\n\nimpl Sizable for Stepper {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Styled for Stepper {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Stepper {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        let total_items = self.items.len();\n        div()\n            .id(self.id)\n            .w_full()\n            .when(self.layout.is_horizontal(), |this| this.h_flex())\n            .when(self.layout.is_vertical(), |this| this.v_flex())\n            .refine_style(&self.style)\n            .children(self.items.into_iter().enumerate().map(|(step, item)| {\n                let is_last = step + 1 == total_items;\n                item.step(step)\n                    .with_size(self.size)\n                    .checked_step(self.step)\n                    .layout(self.layout)\n                    .text_center(self.text_center)\n                    .when(self.disabled, |this| this.disabled(true))\n                    .is_last(is_last)\n                    .on_click({\n                        let on_click = self.on_click.clone();\n                        move |_, window, cx| {\n                            on_click(&step, window, cx);\n                        }\n                    })\n            }))\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/stepper/trigger.rs",
    "content": "use gpui::{\n    AnyElement, App, Axis, ClickEvent, InteractiveElement as _, IntoElement, ParentElement, Pixels,\n    RenderOnce, StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div,\n    prelude::FluentBuilder as _, px,\n};\n\nuse crate::{ActiveTheme as _, AxisExt, Icon, Size, StyleSized, StyledExt as _};\n\n/// The trigger part of a stepper item.\n#[derive(IntoElement)]\npub(super) struct StepperTrigger {\n    step: usize,\n    checked_step: usize,\n    style: StyleRefinement,\n    icon: Option<Icon>,\n    icon_size: Pixels,\n    children: Vec<AnyElement>,\n    layout: Axis,\n    disabled: bool,\n    text_center: bool,\n    size: Size,\n    on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,\n}\n\nimpl StepperTrigger {\n    pub(super) fn new() -> Self {\n        Self {\n            step: 0,\n            checked_step: 0,\n            icon: None,\n            icon_size: px(24.),\n            layout: Axis::Horizontal,\n            disabled: false,\n            size: Size::default(),\n            children: Vec::new(),\n            text_center: false,\n            style: StyleRefinement::default(),\n            on_click: Box::new(|_, _, _| {}),\n        }\n    }\n\n    pub(super) fn step(mut self, ix: usize) -> Self {\n        self.step = ix;\n        self\n    }\n\n    pub(super) fn checked_step(mut self, checked_step: usize) -> Self {\n        self.checked_step = checked_step;\n        self\n    }\n\n    pub(super) fn layout(mut self, layout: Axis) -> Self {\n        self.layout = layout;\n        self\n    }\n\n    pub(super) fn icon(mut self, icon: Option<impl Into<Icon>>) -> Self {\n        self.icon = icon.map(|i| i.into());\n        self\n    }\n\n    pub(super) fn icon_size(mut self, size: Pixels) -> Self {\n        self.icon_size = size;\n        self\n    }\n\n    pub(super) fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    pub(super) fn with_size(mut self, size: Size) -> Self {\n        self.size = size;\n        self\n    }\n\n    pub(super) fn text_center(mut self, center: bool) -> Self {\n        self.text_center = center;\n        self\n    }\n\n    pub(super) fn on_click<F>(mut self, f: F) -> Self\n    where\n        F: Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    {\n        self.on_click = Box::new(f);\n        self\n    }\n}\n\nimpl Styled for StepperTrigger {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl ParentElement for StepperTrigger {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl RenderOnce for StepperTrigger {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let is_checked = self.step <= self.checked_step;\n\n        div()\n            .id((\"trigger\", self.step))\n            .when(self.layout.is_horizontal(), |this| this.v_flex().gap_1())\n            .when(self.layout.is_vertical(), |this| this.h_flex().gap_2())\n            .items_start()\n            .when(self.text_center, |this| this.items_center())\n            .input_text_size(self.size.smaller())\n            .refine_style(&self.style)\n            .child(\n                div()\n                    .id(\"indicator\")\n                    .size(self.icon_size)\n                    .overflow_hidden()\n                    .flex()\n                    .rounded_full()\n                    .items_center()\n                    .justify_center()\n                    .bg(cx.theme().secondary)\n                    .when(!self.disabled && !is_checked, |this| {\n                        this.hover(|this| this.bg(cx.theme().secondary_hover))\n                            .active(|this| this.bg(cx.theme().secondary_active))\n                    })\n                    .text_color(cx.theme().secondary_foreground)\n                    .when(is_checked, |this| {\n                        this.bg(cx.theme().primary)\n                            .text_color(cx.theme().primary_foreground)\n                    })\n                    .when(self.size != Size::XSmall, |this| {\n                        this.map(|this| {\n                            this.child(if let Some(icon) = self.icon {\n                                icon.into_any_element()\n                            } else {\n                                div().child(format!(\"{}\", self.step + 1)).into_any_element()\n                            })\n                        })\n                    }),\n            )\n            .children(self.children)\n            .when(!self.disabled, |this| {\n                this.on_click(move |event, window, cx| {\n                    (self.on_click)(event, window, cx);\n                })\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/styled.rs",
    "content": "use crate::ActiveTheme;\nuse gpui::{\n    App, BoxShadow, Corners, DefiniteLength, Div, Edges, FocusHandle, Hsla, ParentElement, Pixels,\n    Refineable, StyleRefinement, Styled, Window, div, point, px,\n};\nuse serde::{Deserialize, Serialize};\n\n/// Returns a `Div` as horizontal flex layout.\n#[inline(always)]\npub fn h_flex() -> Div {\n    div().h_flex()\n}\n\n/// Returns a `Div` as vertical flex layout.\n#[inline(always)]\npub fn v_flex() -> Div {\n    div().v_flex()\n}\n\n/// Create a [`BoxShadow`] like CSS.\n///\n/// e.g:\n///\n/// If CSS is `box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);`\n///\n/// Then the equivalent in Rust is `box_shadow(0., 0., 10., 0., hsla(0., 0., 0., 0.1))`\n#[inline(always)]\npub fn box_shadow(\n    x: impl Into<Pixels>,\n    y: impl Into<Pixels>,\n    blur: impl Into<Pixels>,\n    spread: impl Into<Pixels>,\n    color: Hsla,\n) -> BoxShadow {\n    BoxShadow {\n        offset: point(x.into(), y.into()),\n        blur_radius: blur.into(),\n        spread_radius: spread.into(),\n        color,\n    }\n}\n\nmacro_rules! font_weight {\n    ($fn:ident, $const:ident) => {\n        /// [docs](https://tailwindcss.com/docs/font-weight)\n        #[inline]\n        fn $fn(self) -> Self {\n            self.font_weight(gpui::FontWeight::$const)\n        }\n    };\n}\n\n/// Extends [`gpui::Styled`] with specific styling methods.\n#[cfg_attr(any(feature = \"inspector\", debug_assertions), gpui_macros::derive_inspector_reflection)]\npub trait StyledExt: Styled + Sized {\n    /// Refine the style of this element, applying the given style refinement.\n    fn refine_style(mut self, style: &StyleRefinement) -> Self {\n        self.style().refine(style);\n        self\n    }\n\n    /// Apply self into a horizontal flex layout.\n    #[inline(always)]\n    fn h_flex(self) -> Self {\n        self.flex().flex_row().items_center()\n    }\n\n    /// Apply self into a vertical flex layout.\n    #[inline(always)]\n    fn v_flex(self) -> Self {\n        self.flex().flex_col()\n    }\n\n    /// Apply paddings to the element.\n    fn paddings<L>(self, paddings: impl Into<Edges<L>>) -> Self\n    where\n        L: Into<DefiniteLength> + Clone + Default + std::fmt::Debug + PartialEq,\n    {\n        let paddings = paddings.into();\n        self.pt(paddings.top.into())\n            .pb(paddings.bottom.into())\n            .pl(paddings.left.into())\n            .pr(paddings.right.into())\n    }\n\n    /// Apply margins to the element.\n    fn margins<L>(self, margins: impl Into<Edges<L>>) -> Self\n    where\n        L: Into<DefiniteLength> + Clone + Default + std::fmt::Debug + PartialEq,\n    {\n        let margins = margins.into();\n        self.mt(margins.top.into())\n            .mb(margins.bottom.into())\n            .ml(margins.left.into())\n            .mr(margins.right.into())\n    }\n\n    /// Render a border with a width of 1px, color red\n    fn debug_red(self) -> Self {\n        if cfg!(debug_assertions) { self.border_1().border_color(crate::red_500()) } else { self }\n    }\n\n    /// Render a border with a width of 1px, color blue\n    fn debug_blue(self) -> Self {\n        if cfg!(debug_assertions) { self.border_1().border_color(crate::blue_500()) } else { self }\n    }\n\n    /// Render a border with a width of 1px, color yellow\n    fn debug_yellow(self) -> Self {\n        if cfg!(debug_assertions) {\n            self.border_1().border_color(crate::yellow_500())\n        } else {\n            self\n        }\n    }\n\n    /// Render a border with a width of 1px, color green\n    fn debug_green(self) -> Self {\n        if cfg!(debug_assertions) { self.border_1().border_color(crate::green_500()) } else { self }\n    }\n\n    /// Render a border with a width of 1px, color pink\n    fn debug_pink(self) -> Self {\n        if cfg!(debug_assertions) { self.border_1().border_color(crate::pink_500()) } else { self }\n    }\n\n    /// Render a 1px blue border, when if the element is focused\n    fn debug_focused(self, focus_handle: &FocusHandle, window: &Window, cx: &App) -> Self {\n        if cfg!(debug_assertions) {\n            if focus_handle.contains_focused(window, cx) { self.debug_blue() } else { self }\n        } else {\n            self\n        }\n    }\n\n    /// Render a border with a width of 1px, color ring color\n    #[inline]\n    fn focused_border(self, cx: &App) -> Self {\n        self.border_1().border_color(cx.theme().ring)\n    }\n\n    font_weight!(font_thin, THIN);\n    font_weight!(font_extralight, EXTRA_LIGHT);\n    font_weight!(font_light, LIGHT);\n    font_weight!(font_normal, NORMAL);\n    font_weight!(font_medium, MEDIUM);\n    font_weight!(font_semibold, SEMIBOLD);\n    font_weight!(font_bold, BOLD);\n    font_weight!(font_extrabold, EXTRA_BOLD);\n    font_weight!(font_black, BLACK);\n\n    /// Set as Popover style\n    #[inline]\n    fn popover_style(self, cx: &App) -> Self {\n        self.bg(cx.theme().popover)\n            .text_color(cx.theme().popover_foreground)\n            .border_1()\n            .border_color(cx.theme().border)\n            .shadow_lg()\n            .rounded(cx.theme().radius)\n    }\n\n    /// Set corner radii for the element.\n    fn corner_radii(self, radius: Corners<Pixels>) -> Self {\n        self.rounded_tl(radius.top_left)\n            .rounded_tr(radius.top_right)\n            .rounded_bl(radius.bottom_left)\n            .rounded_br(radius.bottom_right)\n    }\n}\n\nimpl<E: Styled> StyledExt for E {}\n\n/// A size for elements.\n#[derive(Clone, Default, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)]\npub enum Size {\n    Size(Pixels),\n    XSmall,\n    Small,\n    #[default]\n    Medium,\n    Large,\n}\n\nimpl Size {\n    fn as_f32(&self) -> f32 {\n        match self {\n            Size::Size(val) => val.as_f32(),\n            Size::XSmall => 0.,\n            Size::Small => 1.,\n            Size::Medium => 2.,\n            Size::Large => 3.,\n        }\n    }\n\n    /// Returns the size as a static string.\n    pub fn as_str(&self) -> &'static str {\n        match self {\n            Size::XSmall => \"xs\",\n            Size::Small => \"sm\",\n            Size::Medium => \"md\",\n            Size::Large => \"lg\",\n            Size::Size(_) => \"custom\",\n        }\n    }\n\n    /// Create a Size from a static string.\n    ///\n    /// - \"xs\" or \"xsmall\"\n    /// - \"sm\" or \"small\"\n    /// - \"md\" or \"medium\"\n    /// - \"lg\" or \"large\"\n    ///\n    /// Any other value will return Size::Medium.\n    pub fn from_str(size: &str) -> Self {\n        match size.to_lowercase().as_str() {\n            \"xs\" | \"xsmall\" => Size::XSmall,\n            \"sm\" | \"small\" => Size::Small,\n            \"md\" | \"medium\" => Size::Medium,\n            \"lg\" | \"large\" => Size::Large,\n            _ => Size::Medium,\n        }\n    }\n\n    /// Returns the height for table row.\n    #[inline]\n    pub fn table_row_height(&self) -> Pixels {\n        match self {\n            Size::XSmall => px(26.),\n            Size::Small => px(30.),\n            Size::Large => px(40.),\n            _ => px(32.),\n        }\n    }\n\n    /// Returns the padding for a table cell.\n    #[inline]\n    pub fn table_cell_padding(&self) -> Edges<Pixels> {\n        match self {\n            Size::XSmall => Edges { top: px(2.), bottom: px(2.), left: px(4.), right: px(4.) },\n            Size::Small => Edges { top: px(3.), bottom: px(3.), left: px(6.), right: px(6.) },\n            Size::Large => Edges { top: px(8.), bottom: px(8.), left: px(12.), right: px(12.) },\n            _ => Edges { top: px(4.), bottom: px(4.), left: px(8.), right: px(8.) },\n        }\n    }\n\n    /// Returns a smaller size.\n    pub fn smaller(&self) -> Self {\n        match self {\n            Size::XSmall => Size::XSmall,\n            Size::Small => Size::XSmall,\n            Size::Medium => Size::Small,\n            Size::Large => Size::Medium,\n            Size::Size(val) => Size::Size(*val * 0.2),\n        }\n    }\n\n    /// Returns a larger size.\n    pub fn larger(&self) -> Self {\n        match self {\n            Size::XSmall => Size::Small,\n            Size::Small => Size::Medium,\n            Size::Medium => Size::Large,\n            Size::Large => Size::Large,\n            Size::Size(val) => Size::Size(*val * 1.2),\n        }\n    }\n\n    /// Return the max size between two sizes.\n    ///\n    /// e.g. `Size::XSmall.max(Size::Small)` will return `Size::XSmall`.\n    pub fn max(&self, other: Self) -> Self {\n        match (self, other) {\n            (Size::Size(a), Size::Size(b)) => Size::Size(px(a.as_f32().min(b.as_f32()))),\n            (Size::Size(a), _) => Size::Size(*a),\n            (_, Size::Size(b)) => Size::Size(b),\n            (a, b) if a.as_f32() < b.as_f32() => *a,\n            _ => other,\n        }\n    }\n\n    /// Return the min size between two sizes.\n    ///\n    /// e.g. `Size::XSmall.min(Size::Small)` will return `Size::Small`.\n    pub fn min(&self, other: Self) -> Self {\n        match (self, other) {\n            (Size::Size(a), Size::Size(b)) => Size::Size(px(a.as_f32().max(b.as_f32()))),\n            (Size::Size(a), _) => Size::Size(*a),\n            (_, Size::Size(b)) => Size::Size(b),\n            (a, b) if a.as_f32() > b.as_f32() => *a,\n            _ => other,\n        }\n    }\n\n    /// Returns the horizontal input padding.\n    pub fn input_px(&self) -> Pixels {\n        match self {\n            Self::Large => px(16.),\n            Self::Medium => px(12.),\n            Self::Small => px(8.),\n            Self::XSmall => px(4.),\n            _ => px(8.),\n        }\n    }\n\n    /// Returns the vertical input padding.\n    pub fn input_py(&self) -> Pixels {\n        match self {\n            Size::Large => px(10.),\n            Size::Medium => px(8.),\n            Size::Small => px(2.),\n            Size::XSmall => px(0.),\n            _ => px(2.),\n        }\n    }\n}\n\nimpl From<Pixels> for Size {\n    fn from(size: Pixels) -> Self {\n        Size::Size(size)\n    }\n}\n\n/// A trait for defining element that can be selected.\n#[allow(patterns_in_fns_without_body)]\npub trait Selectable: Sized {\n    /// Set the selected state of the element.\n    fn selected(mut self, selected: bool) -> Self;\n\n    /// Returns true if the element is selected.\n    fn is_selected(&self) -> bool;\n\n    /// Set is the element mouse right clicked, default do nothing.\n    fn secondary_selected(self, _: bool) -> Self {\n        self\n    }\n}\n\n/// A trait for defining element that can be disabled.\n#[allow(patterns_in_fns_without_body)]\npub trait Disableable {\n    /// Set the disabled state of the element.\n    fn disabled(mut self, disabled: bool) -> Self;\n}\n\n/// A trait for setting the size of an element.\n/// Size::Medium is use by default.\n#[allow(patterns_in_fns_without_body)]\npub trait Sizable: Sized {\n    /// Set the ui::Size of this element.\n    ///\n    /// Also can receive a `ButtonSize` to convert to `IconSize`,\n    /// Or a `Pixels` to set a custom size: `px(30.)`\n    fn with_size(mut self, size: impl Into<Size>) -> Self;\n\n    /// Set to Size::XSmall\n    #[inline(always)]\n    fn xsmall(self) -> Self {\n        self.with_size(Size::XSmall)\n    }\n\n    /// Set to Size::Small\n    #[inline(always)]\n    fn small(self) -> Self {\n        self.with_size(Size::Small)\n    }\n\n    /// Set to Size::Large\n    #[inline(always)]\n    fn large(self) -> Self {\n        self.with_size(Size::Large)\n    }\n}\n\n#[allow(unused)]\npub trait StyleSized<T: Styled> {\n    fn input_text_size(self, size: Size) -> Self;\n    fn input_size(self, size: Size) -> Self;\n    fn input_pl(self, size: Size) -> Self;\n    fn input_pr(self, size: Size) -> Self;\n    fn input_px(self, size: Size) -> Self;\n    fn input_py(self, size: Size) -> Self;\n    fn input_h(self, size: Size) -> Self;\n    fn list_size(self, size: Size) -> Self;\n    fn list_px(self, size: Size) -> Self;\n    fn list_py(self, size: Size) -> Self;\n    /// Apply size with the given `Size`.\n    fn size_with(self, size: Size) -> Self;\n    /// Apply the table cell size (Font size, padding) with the given `Size`.\n    fn table_cell_size(self, size: Size) -> Self;\n    fn button_text_size(self, size: Size) -> Self;\n}\n\nimpl<T: Styled> StyleSized<T> for T {\n    #[inline]\n    fn input_text_size(self, size: Size) -> Self {\n        match size {\n            Size::XSmall => self.text_xs(),\n            Size::Small => self.text_sm(),\n            Size::Medium => self.text_sm(),\n            Size::Large => self.text_base(),\n            Size::Size(size) => self.text_size(size * 0.875),\n        }\n    }\n\n    #[inline]\n    fn input_size(self, size: Size) -> Self {\n        self.input_px(size).input_py(size).input_h(size)\n    }\n\n    #[inline]\n    fn input_pl(self, size: Size) -> Self {\n        self.pl(size.input_px())\n    }\n\n    #[inline]\n    fn input_pr(self, size: Size) -> Self {\n        self.pr(size.input_px())\n    }\n\n    #[inline]\n    fn input_px(self, size: Size) -> Self {\n        self.px(size.input_px())\n    }\n\n    #[inline]\n    fn input_py(self, size: Size) -> Self {\n        self.py(size.input_py())\n    }\n\n    #[inline]\n    fn input_h(self, size: Size) -> Self {\n        match size {\n            Size::Large => self.h_11(),\n            Size::Medium => self.h_8(),\n            Size::Small => self.h_6(),\n            Size::XSmall => self.h_5(),\n            _ => self.h_6(),\n        }\n    }\n\n    #[inline]\n    fn list_size(self, size: Size) -> Self {\n        self.list_px(size).list_py(size).input_text_size(size)\n    }\n\n    #[inline]\n    fn list_px(self, size: Size) -> Self {\n        match size {\n            Size::Small => self.px_2(),\n            _ => self.px_3(),\n        }\n    }\n\n    #[inline]\n    fn list_py(self, size: Size) -> Self {\n        match size {\n            Size::Large => self.py_2(),\n            Size::Medium => self.py_1(),\n            Size::Small => self.py_0p5(),\n            _ => self.py_1(),\n        }\n    }\n\n    #[inline]\n    fn size_with(self, size: Size) -> Self {\n        match size {\n            Size::Large => self.size_11(),\n            Size::Medium => self.size_8(),\n            Size::Small => self.size_5(),\n            Size::XSmall => self.size_4(),\n            Size::Size(size) => self.size(size),\n        }\n    }\n\n    #[inline]\n    fn table_cell_size(self, size: Size) -> Self {\n        let padding = size.table_cell_padding();\n        match size {\n            Size::XSmall => self.text_sm(),\n            Size::Small => self.text_sm(),\n            _ => self,\n        }\n        .pl(padding.left)\n        .pr(padding.right)\n        .pt(padding.top)\n        .pb(padding.bottom)\n    }\n\n    fn button_text_size(self, size: Size) -> Self {\n        match size {\n            Size::XSmall => self.text_xs(),\n            Size::Small => self.text_sm(),\n            _ => self.text_base(),\n        }\n    }\n}\n\npub(crate) trait FocusableExt<T: ParentElement + Styled + Sized> {\n    /// Add focus ring to the element.\n    fn focus_ring(self, is_focused: bool, margins: Pixels, window: &Window, cx: &App) -> Self;\n}\n\nimpl<T: ParentElement + Styled + Sized> FocusableExt<T> for T {\n    fn focus_ring(mut self, is_focused: bool, margins: Pixels, window: &Window, cx: &App) -> Self {\n        if !is_focused {\n            return self;\n        }\n\n        const RING_BORDER_WIDTH: Pixels = px(1.5);\n        let rem_size = window.rem_size();\n        let style = self.style();\n\n        let border_widths = Edges::<Pixels> {\n            top: style.border_widths.top.map(|v| v.to_pixels(rem_size)).unwrap_or_default(),\n            bottom: style.border_widths.bottom.map(|v| v.to_pixels(rem_size)).unwrap_or_default(),\n            left: style.border_widths.left.map(|v| v.to_pixels(rem_size)).unwrap_or_default(),\n            right: style.border_widths.right.map(|v| v.to_pixels(rem_size)).unwrap_or_default(),\n        };\n\n        // Update the radius based on element's corner radii and the ring border width.\n        let radius = Corners::<Pixels> {\n            top_left: style\n                .corner_radii\n                .top_left\n                .map(|v| v.to_pixels(rem_size))\n                .unwrap_or_default(),\n            top_right: style\n                .corner_radii\n                .top_right\n                .map(|v| v.to_pixels(rem_size))\n                .unwrap_or_default(),\n            bottom_left: style\n                .corner_radii\n                .bottom_left\n                .map(|v| v.to_pixels(rem_size))\n                .unwrap_or_default(),\n            bottom_right: style\n                .corner_radii\n                .bottom_right\n                .map(|v| v.to_pixels(rem_size))\n                .unwrap_or_default(),\n        }\n        .map(|v| *v + RING_BORDER_WIDTH);\n\n        let mut inner_style = StyleRefinement::default();\n        inner_style.corner_radii.top_left = Some(radius.top_left.into());\n        inner_style.corner_radii.top_right = Some(radius.top_right.into());\n        inner_style.corner_radii.bottom_left = Some(radius.bottom_left.into());\n        inner_style.corner_radii.bottom_right = Some(radius.bottom_right.into());\n\n        let inset = RING_BORDER_WIDTH + margins;\n\n        self.child(\n            div()\n                .flex_none()\n                .absolute()\n                .top(-(inset + border_widths.top))\n                .left(-(inset + border_widths.left))\n                .right(-(inset + border_widths.right))\n                .bottom(-(inset + border_widths.bottom))\n                .border(RING_BORDER_WIDTH)\n                .border_color(cx.theme().ring.alpha(0.2))\n                .refine_style(&inner_style),\n        )\n    }\n}\n\n/// A trait for defining element that can be collapsed.\npub trait Collapsible {\n    fn collapsed(self, collapsed: bool) -> Self;\n    fn is_collapsed(&self) -> bool;\n}\n\n#[cfg(test)]\nmod tests {\n    use gpui::px;\n\n    use crate::Size;\n\n    #[test]\n    fn test_size_max_min() {\n        assert_eq!(Size::Small.min(Size::XSmall), Size::Small);\n        assert_eq!(Size::XSmall.min(Size::Small), Size::Small);\n        assert_eq!(Size::Small.min(Size::Medium), Size::Medium);\n        assert_eq!(Size::Medium.min(Size::Large), Size::Large);\n        assert_eq!(Size::Large.min(Size::Small), Size::Large);\n\n        assert_eq!(Size::Size(px(10.)).min(Size::Size(px(20.))), Size::Size(px(20.)));\n\n        // Min\n        assert_eq!(Size::Small.max(Size::XSmall), Size::XSmall);\n        assert_eq!(Size::XSmall.max(Size::Small), Size::XSmall);\n        assert_eq!(Size::Small.max(Size::Medium), Size::Small);\n        assert_eq!(Size::Medium.max(Size::Large), Size::Medium);\n        assert_eq!(Size::Large.max(Size::Small), Size::Small);\n\n        assert_eq!(Size::Size(px(10.)).max(Size::Size(px(20.))), Size::Size(px(10.)));\n    }\n\n    #[test]\n    fn test_size_as_str() {\n        assert_eq!(Size::XSmall.as_str(), \"xs\");\n        assert_eq!(Size::Small.as_str(), \"sm\");\n        assert_eq!(Size::Medium.as_str(), \"md\");\n        assert_eq!(Size::Large.as_str(), \"lg\");\n        assert_eq!(Size::Size(px(15.)).as_str(), \"custom\");\n    }\n\n    #[test]\n    fn test_size_from_str() {\n        assert_eq!(Size::from_str(\"xs\"), Size::XSmall);\n        assert_eq!(Size::from_str(\"xsmall\"), Size::XSmall);\n        assert_eq!(Size::from_str(\"sm\"), Size::Small);\n        assert_eq!(Size::from_str(\"small\"), Size::Small);\n        assert_eq!(Size::from_str(\"md\"), Size::Medium);\n        assert_eq!(Size::from_str(\"medium\"), Size::Medium);\n        assert_eq!(Size::from_str(\"lg\"), Size::Large);\n        assert_eq!(Size::from_str(\"large\"), Size::Large);\n        assert_eq!(Size::from_str(\"unknown\"), Size::Medium);\n\n        // Case insensitive\n        assert_eq!(Size::from_str(\"XS\"), Size::XSmall);\n        assert_eq!(Size::from_str(\"SMALL\"), Size::Small);\n        assert_eq!(Size::from_str(\"Md\"), Size::Medium);\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/switch.rs",
    "content": "use crate::{\n    h_flex, text::Text, tooltip::Tooltip, ActiveTheme, Disableable, Side, Sizable, Size, StyledExt,\n};\nuse gpui::{\n    div, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, App, ElementId,\n    InteractiveElement, IntoElement, ParentElement as _, RenderOnce, SharedString,\n    StatefulInteractiveElement, StyleRefinement, Styled, Window,\n};\nuse std::{rc::Rc, time::Duration};\n\n/// A Switch element that can be toggled on or off.\n#[derive(IntoElement)]\npub struct Switch {\n    id: ElementId,\n    style: StyleRefinement,\n    checked: bool,\n    disabled: bool,\n    label: Option<Text>,\n    label_side: Side,\n    on_click: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,\n    size: Size,\n    tooltip: Option<SharedString>,\n}\n\nimpl Switch {\n    /// Create a new Switch element.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        let id: ElementId = id.into();\n        Self {\n            id: id.clone(),\n            style: StyleRefinement::default(),\n            checked: false,\n            disabled: false,\n            label: None,\n            on_click: None,\n            label_side: Side::Right,\n            size: Size::Medium,\n            tooltip: None,\n        }\n    }\n\n    /// Set the checked state of the switch.\n    pub fn checked(mut self, checked: bool) -> Self {\n        self.checked = checked;\n        self\n    }\n\n    /// Set the label of the switch.\n    pub fn label(mut self, label: impl Into<Text>) -> Self {\n        self.label = Some(label.into());\n        self\n    }\n\n    /// Add a click handler for the switch.\n    pub fn on_click<F>(mut self, handler: F) -> Self\n    where\n        F: Fn(&bool, &mut Window, &mut App) + 'static,\n    {\n        self.on_click = Some(Rc::new(handler));\n        self\n    }\n\n    /// Set tooltip for the switch.\n    pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {\n        self.tooltip = Some(tooltip.into());\n        self\n    }\n}\n\nimpl Styled for Switch {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Sizable for Switch {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Disableable for Switch {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl RenderOnce for Switch {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let checked = self.checked;\n        let on_click = self.on_click.clone();\n        let toggle_state = window.use_keyed_state(self.id.clone(), cx, |_, _| checked);\n\n        let (bg, toggle_bg) = match checked {\n            true => (cx.theme().primary, cx.theme().switch_thumb),\n            false => (cx.theme().switch, cx.theme().switch_thumb),\n        };\n\n        let (bg, toggle_bg) = if self.disabled {\n            (\n                if checked { bg.alpha(0.5) } else { bg },\n                toggle_bg.alpha(0.35),\n            )\n        } else {\n            (bg, toggle_bg)\n        };\n\n        let (bg_width, bg_height) = match self.size {\n            Size::XSmall | Size::Small => (px(28.), px(16.)),\n            _ => (px(36.), px(20.)),\n        };\n        let bar_width = match self.size {\n            Size::XSmall | Size::Small => px(12.),\n            _ => px(16.),\n        };\n        let inset = px(2.);\n        let radius = if cx.theme().radius >= px(4.) {\n            bg_height\n        } else {\n            cx.theme().radius\n        };\n\n        div().refine_style(&self.style).child(\n            h_flex()\n                .id(self.id.clone())\n                .gap_2()\n                .items_start()\n                .when(self.label_side.is_left(), |this| this.flex_row_reverse())\n                .child(\n                    // Switch Bar\n                    div()\n                        .id(self.id.clone())\n                        .w(bg_width)\n                        .h(bg_height)\n                        .rounded(radius)\n                        .flex()\n                        .items_center()\n                        .border(inset)\n                        .border_color(cx.theme().transparent)\n                        .bg(bg)\n                        .when_some(self.tooltip.clone(), |this, tooltip| {\n                            this.tooltip(move |window, cx| {\n                                Tooltip::new(tooltip.clone()).build(window, cx)\n                            })\n                        })\n                        .child(\n                            // Switch Toggle\n                            div()\n                                .rounded(radius)\n                                .bg(toggle_bg)\n                                .shadow_md()\n                                .size(bar_width)\n                                .map(|this| {\n                                    let prev_checked = toggle_state.read(cx);\n                                    if !self.disabled && *prev_checked != checked {\n                                        let duration = Duration::from_secs_f64(0.15);\n                                        cx.spawn({\n                                            let toggle_state = toggle_state.clone();\n                                            async move |cx| {\n                                                cx.background_executor().timer(duration).await;\n                                                _ = toggle_state\n                                                    .update(cx, |this, _| *this = checked);\n                                            }\n                                        })\n                                        .detach();\n\n                                        this.with_animation(\n                                            ElementId::NamedInteger(\"move\".into(), checked as u64),\n                                            Animation::new(duration),\n                                            move |this, delta| {\n                                                let max_x = bg_width - bar_width - inset * 2;\n                                                let x = if checked {\n                                                    max_x * delta\n                                                } else {\n                                                    max_x - max_x * delta\n                                                };\n                                                this.left(x)\n                                            },\n                                        )\n                                        .into_any_element()\n                                    } else {\n                                        let max_x = bg_width - bar_width - inset * 2;\n                                        let x = if checked { max_x } else { px(0.) };\n                                        this.left(x).into_any_element()\n                                    }\n                                }),\n                        ),\n                )\n                .when_some(self.label, |this, label| {\n                    this.child(div().line_height(bg_height).child(label).map(\n                        |this| match self.size {\n                            Size::XSmall | Size::Small => this.text_sm(),\n                            _ => this.text_base(),\n                        },\n                    ))\n                })\n                .when_some(\n                    on_click\n                        .as_ref()\n                        .map(|c| c.clone())\n                        .filter(|_| !self.disabled),\n                    |this, on_click| {\n                        let toggle_state = toggle_state.clone();\n                        this.on_mouse_down(gpui::MouseButton::Left, move |_, window, cx| {\n                            cx.stop_propagation();\n                            _ = toggle_state.update(cx, |this, _| *this = checked);\n                            on_click(&!checked, window, cx);\n                        })\n                    },\n                ),\n        )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/tab/mod.rs",
    "content": "mod tab;\nmod tab_bar;\n\npub use tab::*;\npub use tab_bar::*;\n"
  },
  {
    "path": "crates/ui/src/tab/tab.rs",
    "content": "use std::rc::Rc;\n\nuse crate::{ActiveTheme, Icon, IconName, Selectable, Sizable, Size, StyledExt, h_flex};\nuse gpui::prelude::FluentBuilder as _;\nuse gpui::{\n    AnyElement, App, ClickEvent, Div, Edges, Hsla, InteractiveElement, IntoElement, MouseButton,\n    ParentElement, Pixels, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window,\n    div, px, relative,\n};\n\n/// Tab variants.\n#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)]\npub enum TabVariant {\n    #[default]\n    Tab,\n    Outline,\n    Pill,\n    Segmented,\n    Underline,\n}\n\nimpl TabVariant {\n    fn height(&self, size: Size) -> Pixels {\n        match size {\n            Size::XSmall => match self {\n                TabVariant::Underline => px(26.),\n                _ => px(20.),\n            },\n            Size::Small => match self {\n                TabVariant::Underline => px(30.),\n                _ => px(24.),\n            },\n            Size::Large => match self {\n                TabVariant::Underline => px(44.),\n                _ => px(36.),\n            },\n            _ => match self {\n                TabVariant::Underline => px(36.),\n                _ => px(32.),\n            },\n        }\n    }\n\n    fn inner_height(&self, size: Size) -> Pixels {\n        match size {\n            Size::XSmall => match self {\n                TabVariant::Tab | TabVariant::Outline | TabVariant::Pill => px(18.),\n                TabVariant::Segmented => px(16.),\n                TabVariant::Underline => px(20.),\n            },\n            Size::Small => match self {\n                TabVariant::Tab | TabVariant::Outline | TabVariant::Pill => px(22.),\n                TabVariant::Segmented => px(18.),\n                TabVariant::Underline => px(22.),\n            },\n            Size::Large => match self {\n                TabVariant::Tab | TabVariant::Outline | TabVariant::Pill => px(36.),\n                TabVariant::Segmented => px(28.),\n                TabVariant::Underline => px(32.),\n            },\n            _ => match self {\n                TabVariant::Tab => px(30.),\n                TabVariant::Outline | TabVariant::Pill => px(26.),\n                TabVariant::Segmented => px(24.),\n                TabVariant::Underline => px(26.),\n            },\n        }\n    }\n\n    /// Default px(12) to match panel px_3, See [`crate::dock::TabPanel`]\n    fn inner_paddings(&self, size: Size) -> Edges<Pixels> {\n        let mut padding_x = match size {\n            Size::XSmall => px(8.),\n            Size::Small => px(10.),\n            Size::Large => px(16.),\n            _ => px(12.),\n        };\n\n        if matches!(self, TabVariant::Underline) {\n            padding_x = px(0.);\n        }\n\n        Edges {\n            left: padding_x,\n            right: padding_x,\n            ..Default::default()\n        }\n    }\n\n    fn inner_margins(&self, size: Size) -> Edges<Pixels> {\n        match size {\n            Size::XSmall => match self {\n                TabVariant::Underline => Edges {\n                    top: px(1.),\n                    bottom: px(2.),\n                    ..Default::default()\n                },\n                _ => Edges::all(px(0.)),\n            },\n            Size::Small => match self {\n                TabVariant::Underline => Edges {\n                    top: px(2.),\n                    bottom: px(3.),\n                    ..Default::default()\n                },\n                _ => Edges::all(px(0.)),\n            },\n            Size::Large => match self {\n                TabVariant::Underline => Edges {\n                    top: px(5.),\n                    bottom: px(6.),\n                    ..Default::default()\n                },\n                _ => Edges::all(px(0.)),\n            },\n            _ => match self {\n                TabVariant::Underline => Edges {\n                    top: px(3.),\n                    bottom: px(4.),\n                    ..Default::default()\n                },\n                _ => Edges::all(px(0.)),\n            },\n        }\n    }\n\n    fn normal(&self, cx: &App) -> TabStyle {\n        match self {\n            TabVariant::Tab => TabStyle {\n                fg: cx.theme().tab_foreground,\n                bg: cx.theme().transparent,\n                borders: Edges {\n                    left: px(1.),\n                    right: px(1.),\n                    ..Default::default()\n                },\n                border_color: cx.theme().transparent,\n                ..Default::default()\n            },\n            TabVariant::Outline => TabStyle {\n                fg: cx.theme().tab_foreground,\n                bg: cx.theme().transparent,\n                borders: Edges::all(px(1.)),\n                border_color: cx.theme().border,\n                ..Default::default()\n            },\n            TabVariant::Pill => TabStyle {\n                fg: cx.theme().foreground,\n                bg: cx.theme().transparent,\n                ..Default::default()\n            },\n            TabVariant::Segmented => TabStyle {\n                fg: cx.theme().tab_foreground,\n                bg: cx.theme().transparent,\n                ..Default::default()\n            },\n            TabVariant::Underline => TabStyle {\n                fg: cx.theme().tab_foreground,\n                bg: cx.theme().transparent,\n                inner_bg: cx.theme().transparent,\n                borders: Edges {\n                    bottom: px(2.),\n                    ..Default::default()\n                },\n                border_color: cx.theme().transparent,\n                ..Default::default()\n            },\n        }\n    }\n\n    fn hovered(&self, selected: bool, cx: &App) -> TabStyle {\n        match self {\n            TabVariant::Tab => TabStyle {\n                fg: cx.theme().tab_foreground,\n                bg: cx.theme().transparent,\n                borders: Edges {\n                    left: px(1.),\n                    right: px(1.),\n                    ..Default::default()\n                },\n                border_color: cx.theme().transparent,\n                ..Default::default()\n            },\n            TabVariant::Outline => TabStyle {\n                fg: cx.theme().secondary_foreground,\n                bg: cx.theme().secondary_hover,\n                borders: Edges::all(px(1.)),\n                border_color: cx.theme().border,\n                ..Default::default()\n            },\n            TabVariant::Pill => TabStyle {\n                fg: cx.theme().secondary_foreground,\n                bg: cx.theme().secondary,\n                ..Default::default()\n            },\n            TabVariant::Segmented => TabStyle {\n                fg: cx.theme().tab_foreground,\n                bg: cx.theme().transparent,\n                inner_bg: if selected {\n                    cx.theme().background\n                } else {\n                    cx.theme().transparent\n                },\n                ..Default::default()\n            },\n            TabVariant::Underline => TabStyle {\n                fg: cx.theme().tab_foreground,\n                bg: cx.theme().transparent,\n                inner_bg: cx.theme().transparent,\n                borders: Edges {\n                    bottom: px(2.),\n                    ..Default::default()\n                },\n                border_color: cx.theme().transparent,\n                ..Default::default()\n            },\n        }\n    }\n\n    fn selected(&self, cx: &App) -> TabStyle {\n        match self {\n            TabVariant::Tab => TabStyle {\n                fg: cx.theme().tab_active_foreground,\n                bg: cx.theme().tab_active,\n                borders: Edges {\n                    left: px(1.),\n                    right: px(1.),\n                    ..Default::default()\n                },\n                border_color: cx.theme().border,\n                ..Default::default()\n            },\n            TabVariant::Outline => TabStyle {\n                fg: cx.theme().primary,\n                bg: cx.theme().transparent,\n                borders: Edges::all(px(1.)),\n                border_color: cx.theme().primary,\n                ..Default::default()\n            },\n            TabVariant::Pill => TabStyle {\n                fg: cx.theme().primary_foreground,\n                bg: cx.theme().primary,\n                ..Default::default()\n            },\n            TabVariant::Segmented => TabStyle {\n                fg: cx.theme().tab_active_foreground,\n                bg: cx.theme().transparent,\n                inner_bg: cx.theme().background,\n                shadow: true,\n                ..Default::default()\n            },\n            TabVariant::Underline => TabStyle {\n                fg: cx.theme().tab_active_foreground,\n                bg: cx.theme().transparent,\n                borders: Edges {\n                    bottom: px(2.),\n                    ..Default::default()\n                },\n                border_color: cx.theme().primary,\n                ..Default::default()\n            },\n        }\n    }\n\n    fn disabled(&self, selected: bool, cx: &App) -> TabStyle {\n        match self {\n            TabVariant::Tab => TabStyle {\n                fg: cx.theme().muted_foreground,\n                bg: cx.theme().transparent,\n                border_color: if selected {\n                    cx.theme().border\n                } else {\n                    cx.theme().transparent\n                },\n                borders: Edges {\n                    left: px(1.),\n                    right: px(1.),\n                    ..Default::default()\n                },\n                ..Default::default()\n            },\n            TabVariant::Outline => TabStyle {\n                fg: cx.theme().muted_foreground,\n                bg: cx.theme().transparent,\n                borders: Edges::all(px(1.)),\n                border_color: if selected {\n                    cx.theme().primary\n                } else {\n                    cx.theme().border\n                },\n                ..Default::default()\n            },\n            TabVariant::Pill => TabStyle {\n                fg: if selected {\n                    cx.theme().primary_foreground.opacity(0.5)\n                } else {\n                    cx.theme().muted_foreground\n                },\n                bg: if selected {\n                    cx.theme().primary.opacity(0.5)\n                } else {\n                    cx.theme().transparent\n                },\n                ..Default::default()\n            },\n            TabVariant::Segmented => TabStyle {\n                fg: cx.theme().muted_foreground,\n                bg: cx.theme().tab_bar,\n                inner_bg: if selected {\n                    cx.theme().background\n                } else {\n                    cx.theme().transparent\n                },\n                ..Default::default()\n            },\n            TabVariant::Underline => TabStyle {\n                fg: cx.theme().muted_foreground,\n                bg: cx.theme().transparent,\n                border_color: if selected {\n                    cx.theme().border\n                } else {\n                    cx.theme().transparent\n                },\n                borders: Edges {\n                    bottom: px(2.),\n                    ..Default::default()\n                },\n                ..Default::default()\n            },\n        }\n    }\n\n    pub(super) fn tab_bar_radius(&self, size: Size, cx: &App) -> Pixels {\n        if *self != TabVariant::Segmented {\n            return px(0.);\n        }\n\n        match size {\n            Size::XSmall | Size::Small => cx.theme().radius,\n            Size::Large => cx.theme().radius_lg,\n            _ => cx.theme().radius_lg,\n        }\n    }\n\n    fn radius(&self, size: Size, cx: &App) -> Pixels {\n        match self {\n            TabVariant::Outline | TabVariant::Pill => px(99.),\n            TabVariant::Segmented => match size {\n                Size::XSmall | Size::Small => cx.theme().radius,\n                Size::Large => cx.theme().radius_lg,\n                _ => cx.theme().radius_lg,\n            },\n            _ => px(0.),\n        }\n    }\n\n    fn inner_radius(&self, size: Size, cx: &App) -> Pixels {\n        match self {\n            TabVariant::Segmented => match size {\n                Size::Large => self.tab_bar_radius(size, cx) - px(3.),\n                _ => self.tab_bar_radius(size, cx) - px(2.),\n            },\n            _ => px(0.),\n        }\n    }\n}\n\n#[allow(dead_code)]\nstruct TabStyle {\n    borders: Edges<Pixels>,\n    border_color: Hsla,\n    bg: Hsla,\n    fg: Hsla,\n    shadow: bool,\n    inner_bg: Hsla,\n}\n\nimpl Default for TabStyle {\n    fn default() -> Self {\n        TabStyle {\n            borders: Edges::all(px(0.)),\n            border_color: gpui::transparent_white(),\n            bg: gpui::transparent_white(),\n            fg: gpui::transparent_white(),\n            shadow: false,\n            inner_bg: gpui::transparent_white(),\n        }\n    }\n}\n\n/// A Tab element for the [`super::TabBar`].\n#[derive(IntoElement)]\npub struct Tab {\n    ix: usize,\n    base: Div,\n    pub(super) label: Option<SharedString>,\n    icon: Option<Icon>,\n    prefix: Option<AnyElement>,\n    pub(super) tab_bar_prefix: Option<bool>,\n    suffix: Option<AnyElement>,\n    children: Vec<AnyElement>,\n    variant: TabVariant,\n    size: Size,\n    pub(super) disabled: bool,\n    pub(super) selected: bool,\n    on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,\n}\n\nimpl From<&'static str> for Tab {\n    fn from(label: &'static str) -> Self {\n        Self::new().label(label)\n    }\n}\n\nimpl From<String> for Tab {\n    fn from(label: String) -> Self {\n        Self::new().label(label)\n    }\n}\n\nimpl From<SharedString> for Tab {\n    fn from(label: SharedString) -> Self {\n        Self::new().label(label)\n    }\n}\n\nimpl From<Icon> for Tab {\n    fn from(icon: Icon) -> Self {\n        Self::default().icon(icon)\n    }\n}\n\nimpl From<IconName> for Tab {\n    fn from(icon_name: IconName) -> Self {\n        Self::default().icon(Icon::new(icon_name))\n    }\n}\n\nimpl Default for Tab {\n    fn default() -> Self {\n        Self {\n            ix: 0,\n            base: div(),\n            label: None,\n            icon: None,\n            tab_bar_prefix: None,\n            children: Vec::new(),\n            disabled: false,\n            selected: false,\n            prefix: None,\n            suffix: None,\n            variant: TabVariant::default(),\n            size: Size::default(),\n            on_click: None,\n        }\n    }\n}\n\nimpl Tab {\n    /// Create a new tab with a label.\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set label for the tab.\n    pub fn label(mut self, label: impl Into<SharedString>) -> Self {\n        self.label = Some(label.into());\n        self\n    }\n\n    /// Set icon for the tab.\n    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {\n        self.icon = Some(icon.into());\n        self\n    }\n\n    /// Set Tab Variant.\n    pub fn with_variant(mut self, variant: TabVariant) -> Self {\n        self.variant = variant;\n        self\n    }\n\n    /// Use Pill variant.\n    pub fn pill(mut self) -> Self {\n        self.variant = TabVariant::Pill;\n        self\n    }\n\n    /// Use outline variant.\n    pub fn outline(mut self) -> Self {\n        self.variant = TabVariant::Outline;\n        self\n    }\n\n    /// Use Segmented variant.\n    pub fn segmented(mut self) -> Self {\n        self.variant = TabVariant::Segmented;\n        self\n    }\n\n    /// Use Underline variant.\n    pub fn underline(mut self) -> Self {\n        self.variant = TabVariant::Underline;\n        self\n    }\n\n    /// Set the left side of the tab\n    pub fn prefix(mut self, prefix: impl IntoElement) -> Self {\n        self.prefix = Some(prefix.into_any_element());\n        self\n    }\n\n    /// Set the right side of the tab\n    pub fn suffix(mut self, suffix: impl IntoElement) -> Self {\n        self.suffix = Some(suffix.into_any_element());\n        self\n    }\n\n    /// Set disabled state to the tab, default false.\n    pub fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n\n    /// Set the click handler for the tab.\n    pub fn on_click(\n        mut self,\n        on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        self.on_click = Some(Rc::new(on_click));\n        self\n    }\n\n    /// Set index to the tab.\n    pub(crate) fn ix(mut self, ix: usize) -> Self {\n        self.ix = ix;\n        self\n    }\n\n    /// Set if the tab bar has a prefix.\n    pub(crate) fn tab_bar_prefix(mut self, tab_bar_prefix: bool) -> Self {\n        self.tab_bar_prefix = Some(tab_bar_prefix);\n        self\n    }\n}\n\nimpl ParentElement for Tab {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Selectable for Tab {\n    fn selected(mut self, selected: bool) -> Self {\n        self.selected = selected;\n        self\n    }\n\n    fn is_selected(&self) -> bool {\n        self.selected\n    }\n}\n\nimpl InteractiveElement for Tab {\n    fn interactivity(&mut self) -> &mut gpui::Interactivity {\n        self.base.interactivity()\n    }\n}\n\nimpl StatefulInteractiveElement for Tab {}\n\nimpl Styled for Tab {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        self.base.style()\n    }\n}\n\nimpl Sizable for Tab {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl RenderOnce for Tab {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let mut tab_style = if self.selected {\n            self.variant.selected(cx)\n        } else {\n            self.variant.normal(cx)\n        };\n        let mut hover_style = self.variant.hovered(self.selected, cx);\n        if self.disabled {\n            tab_style = self.variant.disabled(self.selected, cx);\n            hover_style = self.variant.disabled(self.selected, cx);\n        }\n        let tab_bar_prefix = self.tab_bar_prefix.unwrap_or_default();\n        if !tab_bar_prefix {\n            if self.ix == 0 && self.variant == TabVariant::Tab {\n                tab_style.borders.left = px(0.);\n                hover_style.borders.left = px(0.);\n            }\n        }\n        let radius = self.variant.radius(self.size, cx);\n        let inner_radius = self.variant.inner_radius(self.size, cx);\n        let inner_paddings = self.variant.inner_paddings(self.size);\n        let inner_margins = self.variant.inner_margins(self.size);\n        let inner_height = self.variant.inner_height(self.size);\n        let height = self.variant.height(self.size);\n\n        self.base\n            .id(self.ix)\n            .flex()\n            .flex_wrap()\n            .gap_1()\n            .items_center()\n            .flex_shrink_0()\n            .h(height)\n            .overflow_hidden()\n            .text_color(tab_style.fg)\n            .map(|this| match self.size {\n                Size::XSmall => this.text_xs(),\n                Size::Large => this.text_base(),\n                _ => this.text_sm(),\n            })\n            .bg(tab_style.bg)\n            .border_l(tab_style.borders.left)\n            .border_r(tab_style.borders.right)\n            .border_t(tab_style.borders.top)\n            .border_b(tab_style.borders.bottom)\n            .border_color(tab_style.border_color)\n            .rounded(radius)\n            .when(!self.selected && !self.disabled, |this| {\n                this.hover(|this| {\n                    this.text_color(hover_style.fg)\n                        .bg(hover_style.bg)\n                        .border_l(hover_style.borders.left)\n                        .border_r(hover_style.borders.right)\n                        .border_t(hover_style.borders.top)\n                        .border_b(hover_style.borders.bottom)\n                        .border_color(hover_style.border_color)\n                        .rounded(radius)\n                })\n            })\n            .when_some(self.prefix, |this, prefix| this.child(prefix))\n            .child(\n                h_flex()\n                    .flex_1()\n                    .h(inner_height)\n                    .line_height(relative(1.))\n                    .whitespace_nowrap()\n                    .items_center()\n                    .justify_center()\n                    .overflow_hidden()\n                    .margins(inner_margins)\n                    .flex_shrink_0()\n                    .map(|this| match self.icon {\n                        Some(icon) => {\n                            this.w(inner_height * 1.25)\n                                .child(icon.map(|this| match self.size {\n                                    Size::XSmall => this.size_2p5(),\n                                    Size::Small => this.size_3p5(),\n                                    Size::Large => this.size_4(),\n                                    _ => this.size_4(),\n                                }))\n                        }\n                        None => this\n                            .paddings(inner_paddings)\n                            .map(|this| match self.label {\n                                Some(label) => this.child(label),\n                                None => this,\n                            })\n                            .children(self.children),\n                    })\n                    .bg(tab_style.inner_bg)\n                    .rounded(inner_radius)\n                    .when(tab_style.shadow, |this| this.shadow_xs())\n                    .hover(|this| this.bg(hover_style.inner_bg).rounded(inner_radius)),\n            )\n            .when_some(self.suffix, |this, suffix| this.child(suffix))\n            .on_mouse_down(MouseButton::Left, |_, _, cx| {\n                // Stop propagation behavior, for works on TitleBar.\n                // https://github.com/longbridge/gpui-component/issues/1836\n                cx.stop_propagation();\n            })\n            .when(!self.disabled, |this| {\n                this.when_some(self.on_click.clone(), |this, on_click| {\n                    this.on_click(move |event, window, cx| on_click(event, window, cx))\n                })\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/tab/tab_bar.rs",
    "content": "use gpui::{\n    AnyElement, App, Corner, Div, Edges, ElementId, InteractiveElement, IntoElement, ParentElement,\n    RenderOnce, ScrollHandle, Stateful, StatefulInteractiveElement as _, StyleRefinement, Styled,\n    Window, div, prelude::FluentBuilder as _, px,\n};\nuse smallvec::SmallVec;\nuse std::rc::Rc;\n\nuse super::{Tab, TabVariant};\nuse crate::button::{Button, ButtonVariants as _};\nuse crate::menu::{DropdownMenu as _, PopupMenuItem};\nuse crate::{ActiveTheme, IconName, Selectable, Sizable, Size, StyledExt, h_flex};\n\n/// A TabBar element that contains multiple [`Tab`] items.\n#[derive(IntoElement)]\npub struct TabBar {\n    base: Stateful<Div>,\n    style: StyleRefinement,\n    scroll_handle: Option<ScrollHandle>,\n    prefix: Option<AnyElement>,\n    suffix: Option<AnyElement>,\n    children: SmallVec<[Tab; 2]>,\n    last_empty_space: AnyElement,\n    selected_index: Option<usize>,\n    variant: TabVariant,\n    size: Size,\n    menu: bool,\n    on_click: Option<Rc<dyn Fn(&usize, &mut Window, &mut App) + 'static>>,\n}\n\nimpl TabBar {\n    /// Create a new TabBar.\n    pub fn new(id: impl Into<ElementId>) -> Self {\n        Self {\n            base: div().id(id).px(px(-1.)),\n            style: StyleRefinement::default(),\n            children: SmallVec::new(),\n            scroll_handle: None,\n            prefix: None,\n            suffix: None,\n            variant: TabVariant::default(),\n            size: Size::default(),\n            last_empty_space: div().w_3().into_any_element(),\n            selected_index: None,\n            on_click: None,\n            menu: false,\n        }\n    }\n\n    /// Set the Tab variant, all children will inherit the variant.\n    pub fn with_variant(mut self, variant: TabVariant) -> Self {\n        self.variant = variant;\n        self\n    }\n\n    /// Set the Tab variant to Pill, all children will inherit the variant.\n    pub fn pill(mut self) -> Self {\n        self.variant = TabVariant::Pill;\n        self\n    }\n\n    /// Set the Tab variant to Outline, all children will inherit the variant.\n    pub fn outline(mut self) -> Self {\n        self.variant = TabVariant::Outline;\n        self\n    }\n\n    /// Set the Tab variant to Segmented, all children will inherit the variant.\n    pub fn segmented(mut self) -> Self {\n        self.variant = TabVariant::Segmented;\n        self\n    }\n\n    /// Set the Tab variant to Underline, all children will inherit the variant.\n    pub fn underline(mut self) -> Self {\n        self.variant = TabVariant::Underline;\n        self\n    }\n\n    /// Set whether to show the menu button when tabs overflow, default is false.\n    pub fn menu(mut self, menu: bool) -> Self {\n        self.menu = menu;\n        self\n    }\n\n    /// Track the scroll of the TabBar.\n    pub fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {\n        self.scroll_handle = Some(scroll_handle.clone());\n        self\n    }\n\n    /// Set the prefix element of the TabBar\n    pub fn prefix(mut self, prefix: impl IntoElement) -> Self {\n        self.prefix = Some(prefix.into_any_element());\n        self\n    }\n\n    /// Set the suffix element of the TabBar\n    pub fn suffix(mut self, suffix: impl IntoElement) -> Self {\n        self.suffix = Some(suffix.into_any_element());\n        self\n    }\n\n    /// Add children of the TabBar, all children will inherit the variant.\n    pub fn children(mut self, children: impl IntoIterator<Item = impl Into<Tab>>) -> Self {\n        self.children.extend(children.into_iter().map(Into::into));\n        self\n    }\n\n    /// Add child of the TabBar, tab will inherit the variant.\n    pub fn child(mut self, child: impl Into<Tab>) -> Self {\n        self.children.push(child.into());\n        self\n    }\n\n    /// Set the selected index of the TabBar.\n    pub fn selected_index(mut self, index: usize) -> Self {\n        self.selected_index = Some(index);\n        self\n    }\n\n    /// Set the last empty space element of the TabBar.\n    pub fn last_empty_space(mut self, last_empty_space: impl IntoElement) -> Self {\n        self.last_empty_space = last_empty_space.into_any_element();\n        self\n    }\n\n    /// Set the on_click callback of the TabBar, the first parameter is the index of the clicked tab.\n    ///\n    /// When this is set, the children's on_click will be ignored.\n    pub fn on_click<F>(mut self, on_click: F) -> Self\n    where\n        F: Fn(&usize, &mut Window, &mut App) + 'static,\n    {\n        self.on_click = Some(Rc::new(on_click));\n        self\n    }\n}\n\nimpl Styled for TabBar {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Sizable for TabBar {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl RenderOnce for TabBar {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let default_gap = match self.size {\n            Size::Small | Size::XSmall => px(8.),\n            Size::Large => px(16.),\n            _ => px(12.),\n        };\n        let (bg, paddings, gap) = match self.variant {\n            TabVariant::Tab => {\n                let padding = Edges::all(px(0.));\n                (cx.theme().tab_bar, padding, px(0.))\n            }\n            TabVariant::Outline => {\n                let padding = Edges::all(px(0.));\n                (cx.theme().transparent, padding, default_gap)\n            }\n            TabVariant::Pill => {\n                let padding = Edges::all(px(0.));\n                (cx.theme().transparent, padding, px(4.))\n            }\n            TabVariant::Segmented => {\n                let padding_x = match self.size {\n                    Size::XSmall => px(2.),\n                    Size::Small => px(3.),\n                    _ => px(4.),\n                };\n                let padding = Edges {\n                    left: padding_x,\n                    right: padding_x,\n                    ..Default::default()\n                };\n\n                (cx.theme().tab_bar_segmented, padding, px(2.))\n            }\n            TabVariant::Underline => {\n                // This gap is same as the tab inner_paddings\n                let gap = match self.size {\n                    Size::XSmall => px(10.),\n                    Size::Small => px(12.),\n                    Size::Large => px(20.),\n                    _ => px(16.),\n                };\n\n                (cx.theme().transparent, Edges::all(px(0.)), gap)\n            }\n        };\n\n        let mut item_labels = Vec::new();\n        let selected_index = self.selected_index;\n        let on_click = self.on_click.clone();\n\n        self.base\n            .group(\"tab-bar\")\n            .relative()\n            .flex()\n            .items_center()\n            .bg(bg)\n            .text_color(cx.theme().tab_foreground)\n            .when(\n                self.variant == TabVariant::Underline || self.variant == TabVariant::Tab,\n                |this| {\n                    this.child(\n                        div()\n                            .id(\"border-b\")\n                            .absolute()\n                            .left_0()\n                            .bottom_0()\n                            .size_full()\n                            .border_b_1()\n                            .border_color(cx.theme().border),\n                    )\n                },\n            )\n            .rounded(self.variant.tab_bar_radius(self.size, cx))\n            .paddings(paddings)\n            .refine_style(&self.style)\n            .when_some(self.prefix, |this, prefix| this.child(prefix))\n            .child(\n                h_flex()\n                    .id(\"tabs\")\n                    .flex_1()\n                    .overflow_x_scroll()\n                    .when_some(self.scroll_handle, |this, scroll_handle| {\n                        this.track_scroll(&scroll_handle)\n                    })\n                    .gap(gap)\n                    .children(self.children.into_iter().enumerate().map(|(ix, child)| {\n                        item_labels.push((child.label.clone(), child.disabled));\n                        let tab_bar_prefix = child.tab_bar_prefix.unwrap_or(true);\n                        child\n                            .ix(ix)\n                            .tab_bar_prefix(tab_bar_prefix)\n                            .with_variant(self.variant)\n                            .with_size(self.size)\n                            .when_some(self.selected_index, |this, selected_ix| {\n                                this.selected(selected_ix == ix)\n                            })\n                            .when_some(self.on_click.clone(), move |this, on_click| {\n                                this.on_click(move |_, window, cx| on_click(&ix, window, cx))\n                            })\n                    }))\n                    .when(self.suffix.is_some() || self.menu, |this| {\n                        this.child(self.last_empty_space)\n                    }),\n            )\n            .when(self.menu, |this| {\n                this.child(\n                    Button::new(\"more\")\n                        .xsmall()\n                        .ghost()\n                        .icon(IconName::ChevronDown)\n                        .dropdown_menu(move |mut this, _, _| {\n                            this = this.scrollable(true);\n                            for (ix, (label, disabled)) in item_labels.iter().enumerate() {\n                                this = this.item(\n                                    PopupMenuItem::new(label.clone().unwrap_or_default())\n                                        .checked(selected_index == Some(ix))\n                                        .disabled(*disabled)\n                                        .when_some(on_click.clone(), |this, on_click| {\n                                            this.on_click(move |_, window, cx| {\n                                                on_click(&ix, window, cx)\n                                            })\n                                        }),\n                                )\n                            }\n\n                            this\n                        })\n                        .anchor(Corner::TopRight),\n                )\n            })\n            .when_some(self.suffix, |this, suffix| this.child(suffix))\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/table/column.rs",
    "content": "use std::f32;\n\nuse gpui::{\n    Bounds, Context, Edges, Empty, EntityId, IntoElement, ParentElement as _, Pixels, Render,\n    SharedString, Styled as _, TextAlign, Window, div, prelude::FluentBuilder, px,\n};\n\nuse crate::ActiveTheme as _;\n\n/// Represents a column in a table, used for initializing table columns.\n#[derive(Debug, Clone)]\npub struct Column {\n    /// The unique key of the column.\n    ///\n    /// This is used to identify the column in the table and your data source.\n    ///\n    /// In most cases, it should match the field name in your data source.\n    pub key: SharedString,\n    /// The display name of the column.\n    pub name: SharedString,\n    /// The text alignment of the column.\n    pub align: TextAlign,\n    /// The sorting behavior of the column, if any.\n    ///\n    /// If `None`, the column is not sortable.\n    pub sort: Option<ColumnSort>,\n    /// The padding of the column.\n    pub paddings: Option<Edges<Pixels>>,\n    /// The width of the column.\n    pub width: Pixels,\n    /// Whether the column is fixed, the fixed column will pin at the left side when scrolling horizontally.\n    pub fixed: Option<ColumnFixed>,\n    /// Whether the column is resizable.\n    pub resizable: bool,\n    /// Whether the column is movable.\n    pub movable: bool,\n    /// Whether the column is selectable.\n    ///\n    /// When `true`:\n    /// - In column selection mode: The entire column can be selected\n    /// - In cell selection mode: Individual cells in this column can be selected\n    ///\n    /// When `false`:\n    /// - The column and its cells cannot be selected\n    /// - Useful for action columns (e.g., buttons, checkboxes) that shouldn't participate in selection\n    pub selectable: bool,\n    /// The minimum width of the column.\n    pub min_width: Pixels,\n    /// The maximum width of the column.\n    pub max_width: Pixels,\n}\n\nimpl Default for Column {\n    fn default() -> Self {\n        Self {\n            key: SharedString::new(\"\"),\n            name: SharedString::new(\"\"),\n            align: TextAlign::Left,\n            sort: None,\n            paddings: None,\n            width: px(100.),\n            fixed: None,\n            resizable: true,\n            movable: true,\n            selectable: true,\n            min_width: px(20.0),\n            max_width: px(f32::MAX),\n        }\n    }\n}\n\nimpl Column {\n    /// Create a new column with the given key and name.\n    pub fn new(key: impl Into<SharedString>, name: impl Into<SharedString>) -> Self {\n        Self {\n            key: key.into(),\n            name: name.into(),\n            ..Default::default()\n        }\n    }\n\n    /// Set the column to be sortable with custom sort function, default is None (not sortable).\n    ///\n    /// See also [`Column::sortable`] to enable sorting with default.\n    pub fn sort(mut self, sort: ColumnSort) -> Self {\n        self.sort = Some(sort);\n        self\n    }\n\n    /// Set whether the column is sortable, default is true.\n    ///\n    /// See also [`Column::sort`].\n    pub fn sortable(mut self) -> Self {\n        self.sort = Some(ColumnSort::Default);\n        self\n    }\n\n    /// Set whether the column is sort with ascending order.\n    pub fn ascending(mut self) -> Self {\n        self.sort = Some(ColumnSort::Ascending);\n        self\n    }\n\n    /// Set whether the column is sort with descending order.\n    pub fn descending(mut self) -> Self {\n        self.sort = Some(ColumnSort::Descending);\n        self\n    }\n\n    /// Set the text alignment of the column to center.\n    pub fn text_center(mut self) -> Self {\n        self.align = TextAlign::Center;\n        self\n    }\n\n    /// Set the alignment of the column text, default is left.\n    ///\n    /// Only `text_left`, `text_right` is supported.\n    pub fn text_right(mut self) -> Self {\n        self.align = TextAlign::Right;\n        self\n    }\n\n    /// Set the padding of the column, default is None.\n    pub fn paddings(mut self, paddings: impl Into<Edges<Pixels>>) -> Self {\n        self.paddings = Some(paddings.into());\n        self\n    }\n\n    /// Set the padding of the column to 0px.\n    pub fn p_0(mut self) -> Self {\n        self.paddings = Some(Edges::all(px(0.)));\n        self\n    }\n\n    /// Set the width of the column, default is 100px.\n    pub fn width(mut self, width: impl Into<Pixels>) -> Self {\n        self.width = width.into();\n        self\n    }\n\n    /// Set whether the column is fixed, default is false.\n    pub fn fixed(mut self, fixed: impl Into<ColumnFixed>) -> Self {\n        self.fixed = Some(fixed.into());\n        self\n    }\n\n    /// Set whether the column is fixed on left side, default is false.\n    pub fn fixed_left(mut self) -> Self {\n        self.fixed = Some(ColumnFixed::Left);\n        self\n    }\n\n    /// Set whether the column is resizable, default is true.\n    pub fn resizable(mut self, resizable: bool) -> Self {\n        self.resizable = resizable;\n        self\n    }\n\n    /// Set whether the column is movable, default is true.\n    pub fn movable(mut self, movable: bool) -> Self {\n        self.movable = movable;\n        self\n    }\n\n    /// Set whether the column is selectable, default is true.\n    ///\n    /// When `false`, this column and its cells will not participate in selection:\n    /// - In column selection mode: The column header cannot be clicked to select\n    /// - In cell selection mode: Cells in this column cannot be selected\n    ///\n    /// This is useful for action columns (e.g., with buttons or checkboxes) that\n    /// should not be part of the selection system.\n    ///\n    /// # Example\n    ///\n    /// ```rust,ignore\n    /// Column::new(\"actions\", \"Actions\")\n    ///     .width(px(100.))\n    ///     .selectable(false)  // Prevent selection of action buttons\n    /// ```\n    pub fn selectable(mut self, selectable: bool) -> Self {\n        self.selectable = selectable;\n        self\n    }\n\n    /// Set the minimum width of the column, default is 20px\n    pub fn min_width(mut self, min_width: impl Into<Pixels>) -> Self {\n        let min_width = min_width.into();\n        self.min_width = min_width;\n\n        // If the current width is smaller than the new minimum,\n        // bump the width up to match the minimum.\n        if self.width < min_width {\n            self.width = min_width;\n        }\n        self\n    }\n\n    /// Set the minimum width of the column, default is 1200px\n    pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {\n        let max_width = max_width.into();\n        self.max_width = max_width;\n\n        // If the current width is larger than the new maximum,\n        // pull the width down to match the maximum.\n        if self.width > max_width {\n            self.width = max_width;\n        }\n        self\n    }\n}\n\nimpl FluentBuilder for Column {}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum ColumnFixed {\n    Left,\n}\n\n/// Used to sort the column runtime info in Table internal.\n#[derive(Debug, Clone)]\npub(crate) struct ColGroup {\n    pub(crate) column: Column,\n    /// This is the runtime width of the column, we may update it when the column is resized.\n    ///\n    /// Including the width with next columns by col_span.\n    pub(crate) width: Pixels,\n    /// The bounds of the column in the table after it renders.\n    pub(crate) bounds: Bounds<Pixels>,\n}\n\nimpl ColGroup {\n    pub(crate) fn is_resizable(&self) -> bool {\n        self.column.resizable\n    }\n}\n\n#[derive(Clone)]\npub(crate) struct DragColumn {\n    pub(crate) entity_id: EntityId,\n    pub(crate) name: SharedString,\n    pub(crate) width: Pixels,\n    pub(crate) col_ix: usize,\n}\n\n/// The sorting behavior of a column.\n#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]\npub enum ColumnSort {\n    /// No sorting.\n    #[default]\n    Default,\n    /// Sort in ascending order.\n    Ascending,\n    /// Sort in descending order.\n    Descending,\n}\n\nimpl Render for DragColumn {\n    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .px_4()\n            .py_1()\n            .bg(cx.theme().table_head)\n            .text_color(cx.theme().muted_foreground)\n            .opacity(0.9)\n            .border_1()\n            .border_color(cx.theme().border)\n            .shadow_md()\n            .w(self.width)\n            .min_w(px(100.))\n            .max_w(px(450.))\n            .child(self.name.clone())\n    }\n}\n\n#[derive(Clone)]\npub(crate) struct ResizeColumn(pub (EntityId, usize));\nimpl Render for ResizeColumn {\n    fn render(&mut self, _window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        Empty\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/table/data_table.rs",
    "content": "use crate::{\n    ActiveTheme, Sizable, Size,\n    actions::{\n        Cancel, SelectDown, SelectFirst, SelectLast, SelectNextColumn, SelectPageDown,\n        SelectPageUp, SelectPrevColumn, SelectUp,\n    },\n    table::{TableDelegate, TableState},\n};\nuse gpui::{\n    App, Edges, Entity, Focusable, InteractiveElement, IntoElement, KeyBinding, ParentElement,\n    RenderOnce, Styled, Window, div, prelude::FluentBuilder,\n};\n\nconst CONTEXT: &'static str = \"DataTable\";\npub(super) fn init(cx: &mut App) {\n    cx.bind_keys([\n        KeyBinding::new(\"escape\", Cancel, Some(CONTEXT)),\n        KeyBinding::new(\"up\", SelectUp, Some(CONTEXT)),\n        KeyBinding::new(\"down\", SelectDown, Some(CONTEXT)),\n        KeyBinding::new(\"left\", SelectPrevColumn, Some(CONTEXT)),\n        KeyBinding::new(\"right\", SelectNextColumn, Some(CONTEXT)),\n        KeyBinding::new(\"home\", SelectFirst, Some(CONTEXT)),\n        KeyBinding::new(\"end\", SelectLast, Some(CONTEXT)),\n        KeyBinding::new(\"pageup\", SelectPageUp, Some(CONTEXT)),\n        KeyBinding::new(\"pagedown\", SelectPageDown, Some(CONTEXT)),\n        KeyBinding::new(\"tab\", SelectNextColumn, Some(CONTEXT)),\n        KeyBinding::new(\"shift-tab\", SelectPrevColumn, Some(CONTEXT)),\n    ]);\n}\n\npub(super) struct TableOptions {\n    pub(super) scrollbar_visible: Edges<bool>,\n    /// Set stripe style of the table.\n    pub(super) stripe: bool,\n    /// Set to use border style of the table.\n    pub(super) bordered: bool,\n    /// The cell size of the table.\n    pub(super) size: Size,\n}\n\nimpl Default for TableOptions {\n    fn default() -> Self {\n        Self {\n            scrollbar_visible: Edges::all(true),\n            stripe: false,\n            bordered: true,\n            size: Size::default(),\n        }\n    }\n}\n\n/// A table element with support for row, column, and cell selection.\n///\n/// # Features\n///\n/// - **Multiple Selection Modes**: Support for row, column, and cell selection\n/// - **Cell Selection**: Click to select individual cells, with keyboard navigation\n/// - **Virtual Scrolling**: Efficient rendering of large datasets\n/// - **Resizable Columns**: Drag column borders to resize\n/// - **Movable Columns**: Drag column headers to reorder\n/// - **Fixed Columns**: Pin columns to the left side\n/// - **Sortable Columns**: Click column headers to sort\n/// - **Context Menus**: Right-click support for rows and cells\n///\n/// # Cell Selection Mode\n///\n/// When cell selection is enabled via [`TableState::cell_selectable()`]:\n/// - Click on cells to select them\n/// - A row selector column appears on the left for selecting entire rows\n/// - Keyboard navigation (arrow keys, Tab, Home, End, PageUp, PageDown) works at cell level\n/// - Right-click and double-click events are supported\n///\n/// See [`TableState`] for more details on cell selection.\n///\n/// # Example\n///\n/// ```rust,ignore\n/// let table_state = cx.new(|cx| {\n///     TableState::new(delegate, cx)\n///         .cell_selectable(true)\n///         .row_selectable(true)\n/// });\n///\n/// DataTable::new(&table_state)\n///     .stripe(true)\n///     .bordered(true)\n/// ```\n#[derive(IntoElement)]\npub struct DataTable<D: TableDelegate> {\n    state: Entity<TableState<D>>,\n    options: TableOptions,\n}\n\nimpl<D> DataTable<D>\nwhere\n    D: TableDelegate,\n{\n    /// Create a new DataTable element with the given [`TableState`].\n    pub fn new(state: &Entity<TableState<D>>) -> Self {\n        Self { state: state.clone(), options: TableOptions::default() }\n    }\n\n    /// Set to use stripe style of the table, default to false.\n    pub fn stripe(mut self, stripe: bool) -> Self {\n        self.options.stripe = stripe;\n        self\n    }\n\n    /// Set to use border style of the table, default to true.\n    pub fn bordered(mut self, bordered: bool) -> Self {\n        self.options.bordered = bordered;\n        self\n    }\n\n    /// Set scrollbar visibility.\n    pub fn scrollbar_visible(mut self, vertical: bool, horizontal: bool) -> Self {\n        self.options.scrollbar_visible =\n            Edges { right: vertical, bottom: horizontal, ..Default::default() };\n        self\n    }\n}\n\nimpl<D> Sizable for DataTable<D>\nwhere\n    D: TableDelegate,\n{\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.options.size = size.into();\n        self\n    }\n}\n\nimpl<D> RenderOnce for DataTable<D>\nwhere\n    D: TableDelegate,\n{\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let bordered = self.options.bordered;\n        let focus_handle = self.state.focus_handle(cx);\n        self.state.update(cx, |state, _| {\n            state.options = self.options;\n        });\n\n        div()\n            .id(\"table\")\n            .size_full()\n            .key_context(CONTEXT)\n            .track_focus(&focus_handle)\n            .on_action(window.listener_for(&self.state, TableState::action_cancel))\n            .on_action(window.listener_for(&self.state, TableState::action_select_next))\n            .on_action(window.listener_for(&self.state, TableState::action_select_prev))\n            .on_action(window.listener_for(&self.state, TableState::action_select_next_col))\n            .on_action(window.listener_for(&self.state, TableState::action_select_prev_col))\n            .on_action(window.listener_for(&self.state, TableState::action_select_first_column))\n            .on_action(window.listener_for(&self.state, TableState::action_select_last_column))\n            .on_action(window.listener_for(&self.state, TableState::action_select_page_up))\n            .on_action(window.listener_for(&self.state, TableState::action_select_page_down))\n            .bg(cx.theme().table)\n            .when(bordered, |this| {\n                this.rounded(cx.theme().radius).border_1().border_color(cx.theme().border)\n            })\n            .child(self.state)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/table/delegate.rs",
    "content": "use std::ops::Range;\n\nuse gpui::{\n    App, Context, Div, InteractiveElement as _, IntoElement, ParentElement as _, Stateful,\n    Styled as _, Window, div,\n};\n\nuse crate::{\n    ActiveTheme as _, Icon, IconName, Size, h_flex,\n    menu::PopupMenu,\n    table::{Column, ColumnSort, TableState, loading::Loading},\n};\n\n/// A delegate trait for providing data and rendering for a table.\n#[allow(unused)]\npub trait TableDelegate: Sized + 'static {\n    /// Return the number of columns in the table.\n    fn columns_count(&self, cx: &App) -> usize;\n\n    /// Return the number of rows in the table.\n    fn rows_count(&self, cx: &App) -> usize;\n\n    /// Returns the table column at the given index.\n    ///\n    /// This only call on Table prepare or refresh.\n    fn column(&self, col_ix: usize, cx: &App) -> Column;\n\n    /// Perform sort on the column at the given index.\n    fn perform_sort(\n        &mut self,\n        col_ix: usize,\n        sort: ColumnSort,\n        window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) {\n    }\n\n    /// Render the table head row.\n    fn render_header(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) -> Stateful<Div> {\n        div().id(\"header\")\n    }\n\n    /// Render the header cell at the given column index, default to the column name.\n    fn render_th(\n        &mut self,\n        col_ix: usize,\n        window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) -> impl IntoElement {\n        div()\n            .size_full()\n            .child(self.column(col_ix, cx).name.clone())\n    }\n\n    /// Render the row at the given row and column.\n    ///\n    /// Not include the table head row.\n    fn render_tr(\n        &mut self,\n        row_ix: usize,\n        window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) -> Stateful<Div> {\n        div().id((\"row\", row_ix))\n    }\n\n    /// Render the context menu for the row at the given row index.\n    fn context_menu(\n        &mut self,\n        row_ix: usize,\n        menu: PopupMenu,\n        window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) -> PopupMenu {\n        menu\n    }\n\n    /// Render cell at the given row and column.\n    fn render_td(\n        &mut self,\n        row_ix: usize,\n        col_ix: usize,\n        window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) -> impl IntoElement;\n\n    /// Move the column at the given `col_ix` to insert before the column at the given `to_ix`.\n    fn move_column(\n        &mut self,\n        col_ix: usize,\n        to_ix: usize,\n        window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) {\n    }\n\n    /// Return a Element to show when table is empty.\n    fn render_empty(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) -> impl IntoElement {\n        h_flex()\n            .size_full()\n            .justify_center()\n            .text_color(cx.theme().muted_foreground.opacity(0.6))\n            .child(Icon::new(IconName::Inbox).size_12())\n            .into_any_element()\n    }\n\n    /// Return true to show the loading view.\n    fn loading(&self, cx: &App) -> bool {\n        false\n    }\n\n    /// Return a Element to show when table is loading, default is built-in Skeleton loading view.\n    ///\n    /// The size is the size of the Table.\n    fn render_loading(\n        &mut self,\n        size: Size,\n        window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) -> impl IntoElement {\n        Loading::new().size(size)\n    }\n\n    /// Return true to enable load more data when scrolling to the bottom.\n    ///\n    /// Default: false\n    fn has_more(&self, cx: &App) -> bool {\n        false\n    }\n\n    /// Returns a threshold value (n rows), of course, when scrolling to the bottom,\n    /// the remaining number of rows triggers `load_more`.\n    /// This should smaller than the total number of first load rows.\n    ///\n    /// Default: 20 rows\n    fn load_more_threshold(&self) -> usize {\n        20\n    }\n\n    /// Load more data when the table is scrolled to the bottom.\n    ///\n    /// This will performed in a background task.\n    ///\n    /// This is always called when the table is near the bottom,\n    /// so you must check if there is more data to load or lock the loading state.\n    fn load_more(&mut self, window: &mut Window, cx: &mut Context<TableState<Self>>) {}\n\n    /// Render the last empty column, default to empty.\n    fn render_last_empty_col(\n        &mut self,\n        window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) -> impl IntoElement {\n        h_flex().w_3().h_full().flex_shrink_0()\n    }\n\n    /// Called when the visible range of the rows changed.\n    ///\n    /// NOTE: Make sure this method is fast, because it will be called frequently.\n    ///\n    /// This can used to handle some data update, to only update the visible rows.\n    /// Please ensure that the data is updated in the background task.\n    fn visible_rows_changed(\n        &mut self,\n        visible_range: Range<usize>,\n        window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) {\n    }\n\n    /// Called when the visible range of the columns changed.\n    ///\n    /// NOTE: Make sure this method is fast, because it will be called frequently.\n    ///\n    /// This can used to handle some data update, to only update the visible rows.\n    /// Please ensure that the data is updated in the background task.\n    fn visible_columns_changed(\n        &mut self,\n        visible_range: Range<usize>,\n        window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) {\n    }\n\n    /// Get the text representation of a cell for export purposes (e.g., CSV export).\n    ///\n    /// Returns an empty string by default. Implement this method to support export.\n    /// The text should be formatted as it should appear in the exported data.\n    fn cell_text(&self, row_ix: usize, col_ix: usize, cx: &App) -> String {\n        String::new()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/table/loading.rs",
    "content": "use crate::{h_flex, skeleton::Skeleton, v_flex, ActiveTheme, Size};\nuse gpui::{prelude::FluentBuilder as _, IntoElement, ParentElement as _, RenderOnce, Styled};\n\n#[derive(IntoElement)]\npub(super) struct Loading {\n    size: Size,\n}\n\nimpl Loading {\n    pub(super) fn new() -> Self {\n        Self { size: Size::Medium }\n    }\n\n    pub(super) fn size(mut self, size: Size) -> Self {\n        self.size = size;\n        self\n    }\n}\n\n#[derive(IntoElement)]\nstruct LoadingRow {\n    header: bool,\n    size: Size,\n}\n\nimpl LoadingRow {\n    fn header() -> Self {\n        Self {\n            header: true,\n            size: Size::Medium,\n        }\n    }\n\n    fn row() -> Self {\n        Self {\n            header: false,\n            size: Size::Medium,\n        }\n    }\n\n    fn size(mut self, size: Size) -> Self {\n        self.size = size;\n        self\n    }\n}\n\nimpl RenderOnce for LoadingRow {\n    fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {\n        let paddings = self.size.table_cell_padding();\n        let height = self.size.table_row_height() * 0.5;\n\n        h_flex()\n            .gap_3()\n            .h(self.size.table_row_height())\n            .overflow_hidden()\n            .pt(paddings.top)\n            .pb(paddings.bottom)\n            .pl(paddings.left)\n            .pr(paddings.right)\n            .items_center()\n            .justify_between()\n            .overflow_hidden()\n            .when(self.header, |this| this.bg(cx.theme().table_head))\n            .when(!self.header, |this| {\n                this.border_t_1().border_color(cx.theme().table_row_border)\n            })\n            .child(\n                h_flex()\n                    .gap_3()\n                    .flex_1()\n                    .child(\n                        Skeleton::new()\n                            .when(self.header, |this| this.secondary())\n                            .h(height)\n                            .w_24(),\n                    )\n                    .child(\n                        Skeleton::new()\n                            .when(self.header, |this| this.secondary())\n                            .h(height)\n                            .w_48(),\n                    )\n                    .child(\n                        Skeleton::new()\n                            .when(self.header, |this| this.secondary())\n                            .h(height)\n                            .w_16(),\n                    ),\n            )\n            .child(\n                Skeleton::new()\n                    .when(self.header, |this| this.secondary())\n                    .h(height)\n                    .w_24(),\n            )\n    }\n}\n\nimpl RenderOnce for Loading {\n    fn render(self, _window: &mut gpui::Window, _cx: &mut gpui::App) -> impl IntoElement {\n        v_flex()\n            .gap_0()\n            .child(LoadingRow::header().size(self.size))\n            .child(LoadingRow::row().size(self.size))\n            .child(LoadingRow::row().size(self.size))\n            .child(LoadingRow::row().size(self.size))\n            .child(LoadingRow::row().size(self.size))\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/table/mod.rs",
    "content": "use gpui::App;\n\nmod column;\nmod data_table;\nmod delegate;\nmod loading;\nmod state;\nmod table;\n\npub use column::*;\npub use data_table::*;\npub use delegate::*;\npub use state::*;\npub use table::*;\n\npub(crate) fn init(cx: &mut App) {\n    data_table::init(cx);\n}\n"
  },
  {
    "path": "crates/ui/src/table/state.rs",
    "content": "use std::{ops::Range, rc::Rc, time::Duration};\n\nuse crate::{\n    ActiveTheme, ElementExt, Icon, IconName, StyleSized as _, StyledExt, VirtualListScrollHandle,\n    actions::{\n        Cancel, SelectDown, SelectFirst, SelectLast, SelectNextColumn, SelectPageDown,\n        SelectPageUp, SelectPrevColumn, SelectUp,\n    },\n    h_flex,\n    menu::{ContextMenuExt, PopupMenu},\n    scroll::{ScrollableMask, Scrollbar},\n    v_flex,\n};\nuse gpui::{\n    AppContext, Axis, Bounds, ClickEvent, Context, Div, DragMoveEvent, EventEmitter, FocusHandle,\n    Focusable, InteractiveElement, IntoElement, ListSizingBehavior, MouseButton, MouseDownEvent,\n    ParentElement, Pixels, Point, Render, ScrollStrategy, SharedString, Stateful,\n    StatefulInteractiveElement as _, Styled, Task, UniformListScrollHandle, Window, div,\n    prelude::FluentBuilder, px, uniform_list,\n};\n\nuse super::*;\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\nenum SelectionMode {\n    Column,\n    Row,\n    Cell,\n}\n\nimpl SelectionMode {\n    #[inline(always)]\n    fn is_row(&self) -> bool {\n        matches!(self, SelectionMode::Row)\n    }\n\n    #[inline(always)]\n    fn is_column(&self) -> bool {\n        matches!(self, SelectionMode::Column)\n    }\n\n    #[inline(always)]\n    fn is_cell(&self) -> bool {\n        matches!(self, SelectionMode::Cell)\n    }\n}\n\n/// The Table event.\n#[derive(Clone)]\npub enum TableEvent {\n    /// Single click or move to selected row.\n    SelectRow(usize),\n    /// Double click on the row.\n    DoubleClickedRow(usize),\n    /// Selected column.\n    SelectColumn(usize),\n    /// A cell has been selected (clicked or navigated to via keyboard).\n    ///\n    /// Emitted when a cell is selected in cell selection mode.\n    /// The first `usize` is the row index, and the second `usize` is the column index.\n    ///\n    /// This event is also emitted when navigating between cells using keyboard shortcuts.\n    SelectCell(usize, usize),\n    /// A cell has been double-clicked.\n    ///\n    /// Emitted when a cell is double-clicked in cell selection mode.\n    /// The first `usize` is the row index, and the second `usize` is the column index.\n    ///\n    /// Use this event to trigger actions like opening a detail view or editing the cell content.\n    DoubleClickedCell(usize, usize),\n    /// The column widths have changed.\n    ///\n    /// The `Vec<Pixels>` contains the new widths of all columns.\n    ColumnWidthsChanged(Vec<Pixels>),\n    /// A column has been moved.\n    ///\n    /// The first `usize` is the original index of the column,\n    /// and the second `usize` is the new index of the column.\n    MoveColumn(usize, usize),\n    /// A row has been right-clicked.\n    ///\n    /// Contains the row index, or `None` if right-clicked on an empty area.\n    /// Use this event to show context menus for rows.\n    RightClickedRow(Option<usize>),\n    /// A cell has been right-clicked.\n    ///\n    /// Emitted when a cell is right-clicked in cell selection mode.\n    /// The first `usize` is the row index, and the second `usize` is the column index.\n    ///\n    /// Use this event to show context menus specific to the cell content.\n    /// The right-clicked cell is highlighted with a subtle border until another cell is clicked.\n    RightClickedCell(usize, usize),\n    /// The selection has been cleared.\n    ///\n    /// This event is emitted when the selection is cleared.\n    ClearSelection,\n}\n\n/// The visible range of the rows and columns.\n#[derive(Debug, Default)]\npub struct TableVisibleRange {\n    /// The visible range of the rows.\n    rows: Range<usize>,\n    /// The visible range of the columns.\n    cols: Range<usize>,\n}\n\nimpl TableVisibleRange {\n    /// Returns the visible range of the rows.\n    pub fn rows(&self) -> &Range<usize> {\n        &self.rows\n    }\n\n    /// Returns the visible range of the columns.\n    pub fn cols(&self) -> &Range<usize> {\n        &self.cols\n    }\n}\n\n/// The state for [`DataTable`].\n///\n/// # Selection Modes\n///\n/// The table supports three selection modes:\n/// - **Row Selection**: Select entire rows (default mode)\n/// - **Column Selection**: Select entire columns\n/// - **Cell Selection**: Select individual cells\n///\n/// ## Cell Selection\n///\n/// When `cell_selectable` is enabled, users can:\n/// - Click on cells to select them\n/// - Right-click on cells to mark them for context menus\n/// - Double-click on cells to trigger actions\n/// - Navigate between cells using keyboard (arrow keys, Home, End, PageUp, PageDown, Tab)\n///\n/// When in cell selection mode, a row selector column appears on the left side,\n/// allowing users to select entire rows by clicking on it.\n///\n/// # Events\n///\n/// The table emits the following events related to cell selection:\n/// - [`TableEvent::SelectCell`]: Emitted when a cell is selected\n/// - [`TableEvent::DoubleClickedCell`]: Emitted when a cell is double-clicked\n/// - [`TableEvent::RightClickedCell`]: Emitted when a cell is right-clicked\n///\n/// # Example\n///\n/// ```rust,ignore\n/// let table_state = cx.new(|cx| {\n///     TableState::new(delegate, cx)\n///         .cell_selectable(true)\n///         .row_selectable(true)\n/// });\n///\n/// // Subscribe to cell events\n/// cx.subscribe(&table_state, |this, table, event, cx| {\n///     match event {\n///         TableEvent::SelectCell(row_ix, col_ix) => {\n///             println!(\"Selected cell: ({}, {})\", row_ix, col_ix);\n///         }\n///         TableEvent::DoubleClickedCell(row_ix, col_ix) => {\n///             println!(\"Double-clicked cell: ({}, {})\", row_ix, col_ix);\n///         }\n///         _ => {}\n///     }\n/// });\n/// ```\npub struct TableState<D: TableDelegate> {\n    focus_handle: FocusHandle,\n    delegate: D,\n    pub(super) options: TableOptions,\n    /// The bounds of the table container.\n    bounds: Bounds<Pixels>,\n    /// The bounds of the fixed head cols.\n    fixed_head_cols_bounds: Bounds<Pixels>,\n\n    col_groups: Vec<ColGroup>,\n\n    /// Whether the table can loop selection, default is true.\n    ///\n    /// When the prev/next selection is out of the table bounds, the selection will loop to the other side.\n    pub loop_selection: bool,\n    /// Whether the table can select column.\n    pub col_selectable: bool,\n    /// Whether the table can select row.\n    pub row_selectable: bool,\n    /// Whether the table can select cell, default is true.\n    ///\n    /// When enabled:\n    /// - Users can click on individual cells to select them\n    /// - A row selector column appears on the left for selecting entire rows\n    /// - Keyboard navigation works at the cell level (arrow keys move between cells)\n    /// - Right-click and double-click events are supported for cells\n    pub cell_selectable: bool,\n    /// Whether the table can sort.\n    pub sortable: bool,\n    /// Whether the table can resize columns.\n    pub col_resizable: bool,\n    /// Whether the table can move columns.\n    pub col_movable: bool,\n    /// Enable/disable fixed columns feature.\n    pub col_fixed: bool,\n\n    pub vertical_scroll_handle: UniformListScrollHandle,\n    pub horizontal_scroll_handle: VirtualListScrollHandle,\n\n    selected_row: Option<usize>,\n    selection_mode: SelectionMode,\n    right_clicked_row: Option<usize>,\n    right_clicked_cell: Option<(usize, usize)>,\n    selected_col: Option<usize>,\n    selected_cell: Option<(usize, usize)>,\n\n    /// The column index that is being resized.\n    resizing_col: Option<usize>,\n\n    /// The visible range of the rows and columns.\n    visible_range: TableVisibleRange,\n\n    _measure: Vec<Duration>,\n    _load_more_task: Task<()>,\n}\n\nimpl<D> TableState<D>\nwhere\n    D: TableDelegate,\n{\n    /// Create a new TableState with the given delegate.\n    pub fn new(delegate: D, _: &mut Window, cx: &mut Context<Self>) -> Self {\n        let mut this = Self {\n            focus_handle: cx.focus_handle().tab_stop(true),\n            options: TableOptions::default(),\n            delegate,\n            col_groups: Vec::new(),\n            horizontal_scroll_handle: VirtualListScrollHandle::new(),\n            vertical_scroll_handle: UniformListScrollHandle::new(),\n            selection_mode: SelectionMode::Row,\n            selected_row: None,\n            right_clicked_row: None,\n            right_clicked_cell: None,\n            selected_col: None,\n            selected_cell: None,\n            resizing_col: None,\n            bounds: Bounds::default(),\n            fixed_head_cols_bounds: Bounds::default(),\n            visible_range: TableVisibleRange::default(),\n            loop_selection: true,\n            col_selectable: true,\n            row_selectable: true,\n            cell_selectable: false,\n            sortable: true,\n            col_movable: true,\n            col_resizable: true,\n            col_fixed: true,\n            _load_more_task: Task::ready(()),\n            _measure: Vec::new(),\n        };\n\n        this.prepare_col_groups(cx);\n        this\n    }\n\n    /// Returns a reference to the delegate.\n    pub fn delegate(&self) -> &D {\n        &self.delegate\n    }\n\n    /// Returns a mutable reference to the delegate.\n    pub fn delegate_mut(&mut self) -> &mut D {\n        &mut self.delegate\n    }\n\n    /// Set to loop selection, default to true.\n    pub fn loop_selection(mut self, loop_selection: bool) -> Self {\n        self.loop_selection = loop_selection;\n        self\n    }\n\n    /// Set to enable/disable column movable, default to true.\n    pub fn col_movable(mut self, col_movable: bool) -> Self {\n        self.col_movable = col_movable;\n        self\n    }\n\n    /// Set to enable/disable column resizable, default to true.\n    pub fn col_resizable(mut self, col_resizable: bool) -> Self {\n        self.col_resizable = col_resizable;\n        self\n    }\n\n    /// Set to enable/disable column sortable, default true\n    pub fn sortable(mut self, sortable: bool) -> Self {\n        self.sortable = sortable;\n        self\n    }\n\n    /// Set to enable/disable row selectable, default true\n    pub fn row_selectable(mut self, row_selectable: bool) -> Self {\n        self.row_selectable = row_selectable;\n        self\n    }\n\n    /// Set to enable/disable column selectable, default true\n    pub fn col_selectable(mut self, col_selectable: bool) -> Self {\n        self.col_selectable = col_selectable;\n        self\n    }\n\n    /// Set to enable/disable cell selection, default is true.\n    ///\n    /// When enabled:\n    /// - Individual cells become selectable by clicking\n    /// - A row selector column appears on the left side\n    /// - Keyboard navigation operates at the cell level\n    /// - Cell-specific events (SelectCell, DoubleClickedCell, RightClickedCell) are emitted\n    ///\n    /// # Example\n    ///\n    /// ```rust,ignore\n    /// let table_state = cx.new(|cx| {\n    ///     TableState::new(delegate, cx)\n    ///         .cell_selectable(true)  // Enable cell selection\n    ///         .row_selectable(true)   // Also allow row selection via row selector\n    /// });\n    /// ```\n    pub fn cell_selectable(mut self, cell_selectable: bool) -> Self {\n        self.cell_selectable = cell_selectable;\n        self\n    }\n\n    /// When we update columns or rows, we need to refresh the table.\n    pub fn refresh(&mut self, cx: &mut Context<Self>) {\n        self.prepare_col_groups(cx);\n    }\n\n    /// Scroll to the row at the given index.\n    pub fn scroll_to_row(&mut self, row_ix: usize, cx: &mut Context<Self>) {\n        self.vertical_scroll_handle.scroll_to_item(row_ix, ScrollStrategy::Top);\n        cx.notify();\n    }\n\n    // Scroll to the column at the given index.\n    pub fn scroll_to_col(&mut self, col_ix: usize, cx: &mut Context<Self>) {\n        let col_ix = col_ix.saturating_sub(self.fixed_left_cols_count());\n\n        self.horizontal_scroll_handle.scroll_to_item(col_ix, ScrollStrategy::Top);\n        cx.notify();\n    }\n\n    /// Returns the selected row index.\n    pub fn selected_row(&self) -> Option<usize> {\n        self.selected_row\n    }\n\n    /// Sets the selected row to the given index.\n    pub fn set_selected_row(&mut self, row_ix: usize, cx: &mut Context<Self>) {\n        let is_down = match self.selected_row {\n            Some(selected_row) => row_ix > selected_row,\n            None => true,\n        };\n\n        cx.stop_propagation();\n        self.selection_mode = SelectionMode::Row;\n        self.right_clicked_row = None;\n        self.selected_row = Some(row_ix);\n        if let Some(row_ix) = self.selected_row {\n            self.vertical_scroll_handle.scroll_to_item(\n                row_ix,\n                if is_down { ScrollStrategy::Bottom } else { ScrollStrategy::Top },\n            );\n        }\n        cx.emit(TableEvent::SelectRow(row_ix));\n        cx.emit(TableEvent::RightClickedRow(None));\n        cx.notify();\n    }\n\n    /// Returns the row that has been right clicked.\n    pub fn right_clicked_row(&self) -> Option<usize> {\n        self.right_clicked_row\n    }\n\n    /// Returns the selected column index.\n    pub fn selected_col(&self) -> Option<usize> {\n        self.selected_col\n    }\n\n    /// Sets the selected col to the given index.\n    pub fn set_selected_col(&mut self, col_ix: usize, cx: &mut Context<Self>) {\n        self.selection_mode = SelectionMode::Column;\n        self.selected_col = Some(col_ix);\n        if let Some(col_ix) = self.selected_col {\n            self.scroll_to_col(col_ix, cx);\n        }\n        cx.emit(TableEvent::SelectColumn(col_ix));\n        cx.notify();\n    }\n\n    /// Returns the selected cell as `(row_ix, col_ix)`.\n    ///\n    /// Returns `None` if no cell is currently selected or if the table is in row/column selection mode.\n    ///\n    /// # Example\n    ///\n    /// ```rust,ignore\n    /// if let Some((row_ix, col_ix)) = table_state.read(cx).selected_cell() {\n    ///     println!(\"Selected cell: ({}, {})\", row_ix, col_ix);\n    /// }\n    /// ```\n    pub fn selected_cell(&self) -> Option<(usize, usize)> {\n        self.selected_cell\n    }\n\n    /// Sets the selected cell to the given row and column indices.\n    ///\n    /// This method:\n    /// - Switches the table to cell selection mode\n    /// - Scrolls to make the cell visible (centered vertically)\n    /// - Emits a [`TableEvent::SelectCell`] event\n    ///\n    /// # Example\n    ///\n    /// ```rust,ignore\n    /// // Select the cell at row 5, column 3\n    /// table_state.update(cx, |state, cx| {\n    ///     state.set_selected_cell(5, 3, cx);\n    /// });\n    /// ```\n    pub fn set_selected_cell(&mut self, row_ix: usize, col_ix: usize, cx: &mut Context<Self>) {\n        self.selection_mode = SelectionMode::Cell;\n        self.selected_cell = Some((row_ix, col_ix));\n\n        // Scroll to the cell\n        self.vertical_scroll_handle.scroll_to_item(row_ix, ScrollStrategy::Center);\n        self.scroll_to_col(col_ix, cx);\n\n        cx.emit(TableEvent::SelectCell(row_ix, col_ix));\n        cx.notify();\n    }\n\n    /// Clear the selection of the table.\n    pub fn clear_selection(&mut self, cx: &mut Context<Self>) {\n        self.selection_mode = SelectionMode::Row;\n        self.selected_row = None;\n        self.selected_col = None;\n        self.selected_cell = None;\n        cx.emit(TableEvent::ClearSelection);\n        cx.notify();\n    }\n\n    /// Returns the visible range of the rows and columns.\n    ///\n    /// See [`TableVisibleRange`].\n    pub fn visible_range(&self) -> &TableVisibleRange {\n        &self.visible_range\n    }\n\n    /// Dump table data.\n    ///\n    /// Returns a tuple of (headers, rows) where each row is a vector of cell values.\n    pub fn dump(&self, cx: &App) -> (Vec<String>, Vec<Vec<String>>) {\n        // Get header row\n        let columns_count = self.delegate.columns_count(cx);\n        let mut headers = Vec::with_capacity(columns_count);\n        for col_ix in 0..columns_count {\n            let column = self.delegate.column(col_ix, cx);\n            headers.push(column.name.to_string());\n        }\n\n        // Get data rows\n        let rows_count = self.delegate.rows_count(cx);\n        let mut rows = Vec::with_capacity(rows_count);\n        for row_ix in 0..rows_count {\n            let mut row = Vec::with_capacity(columns_count);\n            for col_ix in 0..columns_count {\n                row.push(self.delegate.cell_text(row_ix, col_ix, cx));\n            }\n            rows.push(row);\n        }\n\n        (headers, rows)\n    }\n\n    fn prepare_col_groups(&mut self, cx: &mut Context<Self>) {\n        self.col_groups = (0..self.delegate.columns_count(cx))\n            .map(|col_ix| {\n                let column = self.delegate().column(col_ix, cx);\n                ColGroup { width: column.width, bounds: Bounds::default(), column }\n            })\n            .collect();\n        cx.notify();\n    }\n\n    fn fixed_left_cols_count(&self) -> usize {\n        if !self.col_fixed {\n            return 0;\n        }\n\n        self.col_groups.iter().filter(|col| col.column.fixed == Some(ColumnFixed::Left)).count()\n    }\n\n    fn page_item_count(&self) -> usize {\n        let row_height = self.options.size.table_row_height();\n        let height = self.bounds.size.height;\n        let count = (height / row_height).floor() as usize;\n        count.saturating_sub(1).max(1)\n    }\n\n    fn on_row_right_click(\n        &mut self,\n        _: &MouseDownEvent,\n        row_ix: Option<usize>,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.right_clicked_row = row_ix;\n        self.right_clicked_cell = None;\n        cx.emit(TableEvent::RightClickedRow(row_ix));\n    }\n\n    fn on_cell_right_click(\n        &mut self,\n        _: &MouseDownEvent,\n        row_ix: usize,\n        col_ix: usize,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if !self.cell_selectable {\n            return;\n        }\n\n        cx.stop_propagation();\n        self.right_clicked_cell = Some((row_ix, col_ix));\n        self.right_clicked_row = None;\n        cx.emit(TableEvent::RightClickedCell(row_ix, col_ix));\n    }\n\n    fn on_row_left_click(\n        &mut self,\n        e: &ClickEvent,\n        row_ix: usize,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if !self.row_selectable {\n            return;\n        }\n\n        self.set_selected_row(row_ix, cx);\n\n        if e.click_count() == 2 {\n            cx.emit(TableEvent::DoubleClickedRow(row_ix));\n        }\n    }\n\n    fn on_col_head_click(&mut self, col_ix: usize, _: &mut Window, cx: &mut Context<Self>) {\n        if !self.col_selectable {\n            return;\n        }\n\n        let Some(col_group) = self.col_groups.get(col_ix) else {\n            return;\n        };\n\n        if !col_group.column.selectable {\n            return;\n        }\n\n        self.set_selected_col(col_ix, cx)\n    }\n\n    fn on_cell_click(\n        &mut self,\n        e: &ClickEvent,\n        row_ix: usize,\n        col_ix: usize,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if !self.cell_selectable {\n            return;\n        }\n\n        cx.stop_propagation();\n        self.set_selected_cell(row_ix, col_ix, cx);\n\n        if e.click_count() == 2 {\n            cx.emit(TableEvent::DoubleClickedCell(row_ix, col_ix));\n        }\n    }\n\n    fn has_selection(&self) -> bool {\n        self.selected_row.is_some() || self.selected_col.is_some() || self.selected_cell.is_some()\n    }\n\n    pub(super) fn action_cancel(&mut self, _: &Cancel, _: &mut Window, cx: &mut Context<Self>) {\n        if self.has_selection() {\n            self.clear_selection(cx);\n            return;\n        }\n        cx.propagate();\n    }\n\n    pub(super) fn action_select_prev(\n        &mut self,\n        _: &SelectUp,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let rows_count = self.delegate.rows_count(cx);\n        if rows_count < 1 {\n            return;\n        }\n\n        // Cell selection mode: move up within the same column\n        if self.selection_mode.is_cell() {\n            if let Some((row_ix, col_ix)) = self.selected_cell {\n                let new_row = if row_ix > 0 {\n                    row_ix.saturating_sub(1)\n                } else if self.loop_selection {\n                    rows_count.saturating_sub(1)\n                } else {\n                    row_ix\n                };\n                self.set_selected_cell(new_row, col_ix, cx);\n            } else {\n                // No cell selected, select first cell\n                self.set_selected_cell(0, 0, cx);\n            }\n            return;\n        }\n\n        // Row selection mode\n        let mut selected_row = self.selected_row.unwrap_or(0);\n        if selected_row > 0 {\n            selected_row = selected_row.saturating_sub(1);\n        } else {\n            if self.loop_selection {\n                selected_row = rows_count.saturating_sub(1);\n            }\n        }\n\n        self.set_selected_row(selected_row, cx);\n    }\n\n    pub(super) fn action_select_next(\n        &mut self,\n        _: &SelectDown,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let rows_count = self.delegate.rows_count(cx);\n        if rows_count < 1 {\n            return;\n        }\n\n        // Cell selection mode: move down within the same column\n        if self.selection_mode.is_cell() {\n            if let Some((row_ix, col_ix)) = self.selected_cell {\n                let new_row = if row_ix < rows_count.saturating_sub(1) {\n                    row_ix + 1\n                } else if self.loop_selection {\n                    0\n                } else {\n                    row_ix\n                };\n                self.set_selected_cell(new_row, col_ix, cx);\n            } else {\n                // No cell selected, select first cell\n                self.set_selected_cell(0, 0, cx);\n            }\n            return;\n        }\n\n        // Row selection mode\n        let selected_row = match self.selected_row {\n            Some(selected_row) if selected_row < rows_count.saturating_sub(1) => selected_row + 1,\n            Some(selected_row) => {\n                if self.loop_selection {\n                    0\n                } else {\n                    selected_row\n                }\n            }\n            _ => 0,\n        };\n\n        self.set_selected_row(selected_row, cx);\n    }\n\n    pub(super) fn action_select_first_column(\n        &mut self,\n        _: &SelectFirst,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        // Cell selection mode: move to first cell in current row\n        if self.selection_mode.is_cell() {\n            if let Some((row_ix, _)) = self.selected_cell {\n                self.set_selected_cell(row_ix, 0, cx);\n            } else {\n                // No cell selected, select first cell of first row\n                self.set_selected_cell(0, 0, cx);\n            }\n            return;\n        }\n\n        // Column selection mode\n        self.set_selected_col(0, cx);\n    }\n\n    pub(super) fn action_select_last_column(\n        &mut self,\n        _: &SelectLast,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let columns_count = self.delegate.columns_count(cx);\n\n        // Cell selection mode: move to last cell in current row\n        if self.selection_mode.is_cell() {\n            if let Some((row_ix, _)) = self.selected_cell {\n                self.set_selected_cell(row_ix, columns_count.saturating_sub(1), cx);\n            } else {\n                // No cell selected, select last cell of first row\n                self.set_selected_cell(0, columns_count.saturating_sub(1), cx);\n            }\n            return;\n        }\n\n        // Column selection mode\n        self.set_selected_col(columns_count.saturating_sub(1), cx);\n    }\n\n    pub(super) fn action_select_page_up(\n        &mut self,\n        _: &SelectPageUp,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let step = self.page_item_count();\n\n        // Cell selection mode: move up by page within the same column\n        if self.selection_mode.is_cell() {\n            if let Some((row_ix, col_ix)) = self.selected_cell {\n                let target = row_ix.saturating_sub(step);\n                self.set_selected_cell(target, col_ix, cx);\n            } else {\n                // No cell selected, select first cell\n                self.set_selected_cell(0, 0, cx);\n            }\n            return;\n        }\n\n        // Row selection mode\n        let current = self.selected_row.unwrap_or(0);\n        let target = current.saturating_sub(step);\n        self.set_selected_row(target, cx);\n    }\n\n    pub(super) fn action_select_page_down(\n        &mut self,\n        _: &SelectPageDown,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let rows_count = self.delegate.rows_count(cx);\n        if rows_count == 0 {\n            return;\n        }\n\n        let step = self.page_item_count();\n\n        // Cell selection mode: move down by page within the same column\n        if self.selection_mode.is_cell() {\n            if let Some((row_ix, col_ix)) = self.selected_cell {\n                let max_row = rows_count.saturating_sub(1);\n                let target = (row_ix + step).min(max_row);\n                self.set_selected_cell(target, col_ix, cx);\n            } else {\n                // No cell selected, select first cell\n                self.set_selected_cell(0, 0, cx);\n            }\n            return;\n        }\n\n        // Row selection mode\n        let current = self.selected_row.unwrap_or(0);\n        let max_row = rows_count.saturating_sub(1);\n        let target = (current + step).min(max_row);\n        self.set_selected_row(target, cx);\n    }\n\n    pub(super) fn action_select_prev_col(\n        &mut self,\n        _: &SelectPrevColumn,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let columns_count = self.delegate.columns_count(cx);\n\n        // Cell selection mode: move left within the same row\n        if self.selection_mode.is_cell() {\n            if let Some((row_ix, col_ix)) = self.selected_cell {\n                let new_col = if col_ix > 0 {\n                    col_ix.saturating_sub(1)\n                } else if self.loop_selection {\n                    columns_count.saturating_sub(1)\n                } else {\n                    col_ix\n                };\n                self.set_selected_cell(row_ix, new_col, cx);\n            } else {\n                // No cell selected, select first cell\n                self.set_selected_cell(0, 0, cx);\n            }\n            return;\n        }\n\n        // Column selection mode\n        let mut selected_col = self.selected_col.unwrap_or(0);\n        if selected_col > 0 {\n            selected_col = selected_col.saturating_sub(1);\n        } else {\n            if self.loop_selection {\n                selected_col = columns_count.saturating_sub(1);\n            }\n        }\n        self.set_selected_col(selected_col, cx);\n    }\n\n    pub(super) fn action_select_next_col(\n        &mut self,\n        _: &SelectNextColumn,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let columns_count = self.delegate.columns_count(cx);\n\n        // Cell selection mode: move right within the same row\n        if self.selection_mode.is_cell() {\n            if let Some((row_ix, col_ix)) = self.selected_cell {\n                let new_col = if col_ix < columns_count.saturating_sub(1) {\n                    col_ix + 1\n                } else if self.loop_selection {\n                    0\n                } else {\n                    col_ix\n                };\n                self.set_selected_cell(row_ix, new_col, cx);\n            } else {\n                // No cell selected, select first cell\n                self.set_selected_cell(0, 0, cx);\n            }\n            return;\n        }\n\n        // Column selection mode\n        let mut selected_col = self.selected_col.unwrap_or(0);\n        if selected_col < columns_count.saturating_sub(1) {\n            selected_col += 1;\n        } else {\n            if self.loop_selection {\n                selected_col = 0;\n            }\n        }\n\n        self.set_selected_col(selected_col, cx);\n    }\n\n    /// Scroll table when mouse position is near the edge of the table bounds.\n    fn scroll_table_by_col_resizing(\n        &mut self,\n        mouse_position: Point<Pixels>,\n        col_group: &ColGroup,\n    ) {\n        // Do nothing if pos out of the table bounds right for avoid scroll to the right.\n        if mouse_position.x > self.bounds.right() {\n            return;\n        }\n\n        let mut offset = self.horizontal_scroll_handle.offset();\n        let col_bounds = col_group.bounds;\n\n        if mouse_position.x < self.bounds.left()\n            && col_bounds.right() < self.bounds.left() + px(20.)\n        {\n            offset.x += px(1.);\n        } else if mouse_position.x > self.bounds.right()\n            && col_bounds.right() > self.bounds.right() - px(20.)\n        {\n            offset.x -= px(1.);\n        }\n\n        self.horizontal_scroll_handle.set_offset(offset);\n    }\n\n    /// The `ix`` is the index of the col to resize,\n    /// and the `size` is the new size for the col.\n    fn resize_cols(&mut self, ix: usize, size: Pixels, _: &mut Window, cx: &mut Context<Self>) {\n        if !self.col_resizable {\n            return;\n        }\n\n        let Some(col_group) = self.col_groups.get_mut(ix) else {\n            return;\n        };\n\n        if !col_group.is_resizable() {\n            return;\n        }\n\n        let new_width = size.clamp(col_group.column.min_width, col_group.column.max_width);\n\n        // Only update if it actually changed\n        if col_group.width != new_width {\n            col_group.width = new_width;\n            cx.notify();\n        }\n    }\n\n    fn perform_sort(&mut self, col_ix: usize, window: &mut Window, cx: &mut Context<Self>) {\n        if !self.sortable {\n            return;\n        }\n\n        let sort = self.col_groups.get(col_ix).and_then(|g| g.column.sort);\n        if sort.is_none() {\n            return;\n        }\n\n        let sort = sort.unwrap();\n        let sort = match sort {\n            ColumnSort::Ascending => ColumnSort::Default,\n            ColumnSort::Descending => ColumnSort::Ascending,\n            ColumnSort::Default => ColumnSort::Descending,\n        };\n\n        for (ix, col_group) in self.col_groups.iter_mut().enumerate() {\n            if ix == col_ix {\n                col_group.column.sort = Some(sort);\n            } else {\n                if col_group.column.sort.is_some() {\n                    col_group.column.sort = Some(ColumnSort::Default);\n                }\n            }\n        }\n\n        self.delegate_mut().perform_sort(col_ix, sort, window, cx);\n\n        cx.notify();\n    }\n\n    fn move_column(\n        &mut self,\n        col_ix: usize,\n        to_ix: usize,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        if col_ix == to_ix {\n            return;\n        }\n\n        self.delegate.move_column(col_ix, to_ix, window, cx);\n        let col_group = self.col_groups.remove(col_ix);\n        self.col_groups.insert(to_ix, col_group);\n\n        cx.emit(TableEvent::MoveColumn(col_ix, to_ix));\n        cx.notify();\n    }\n\n    /// Dispatch delegate's `load_more` method when the visible range is near the end.\n    fn load_more_if_need(\n        &mut self,\n        rows_count: usize,\n        visible_end: usize,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        let threshold = self.delegate.load_more_threshold();\n        // Securely handle subtract logic to prevent attempt to subtract with overflow\n        if visible_end >= rows_count.saturating_sub(threshold) {\n            if !self.delegate.has_more(cx) {\n                return;\n            }\n\n            self._load_more_task = cx.spawn_in(window, async move |view, window| {\n                _ = view.update_in(window, |view, window, cx| {\n                    view.delegate.load_more(window, cx);\n                });\n            });\n        }\n    }\n\n    fn update_visible_range_if_need(\n        &mut self,\n        visible_range: Range<usize>,\n        axis: Axis,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        // Skip when visible range is only 1 item.\n        // The visual_list will use first item to measure.\n        if visible_range.len() <= 1 {\n            return;\n        }\n\n        if axis == Axis::Vertical {\n            if self.visible_range.rows == visible_range {\n                return;\n            }\n            self.delegate_mut().visible_rows_changed(visible_range.clone(), window, cx);\n            self.visible_range.rows = visible_range;\n        } else {\n            if self.visible_range.cols == visible_range {\n                return;\n            }\n            self.delegate_mut().visible_columns_changed(visible_range.clone(), window, cx);\n            self.visible_range.cols = visible_range;\n        }\n    }\n\n    fn render_cell(\n        &self,\n        _row_ix: Option<usize>,\n        col_ix: usize,\n        _window: &mut Window,\n        _cx: &mut Context<Self>,\n    ) -> Div {\n        let Some(col_group) = self.col_groups.get(col_ix) else {\n            return div();\n        };\n\n        let col_width = col_group.width;\n        let col_padding = col_group.column.paddings;\n\n        div()\n            .w(col_width)\n            .h_full()\n            .flex_shrink_0()\n            .overflow_hidden()\n            .whitespace_nowrap()\n            .table_cell_size(self.options.size)\n            .map(|this| match col_padding {\n                Some(padding) => {\n                    this.pl(padding.left).pr(padding.right).pt(padding.top).pb(padding.bottom)\n                }\n                None => this,\n            })\n    }\n\n    /// Show Column selection style, when the column is selected and the selection state is Column.\n    /// Note: When a cell is selected, column selection style is not shown.\n    fn render_col_wrap(\n        &self,\n        _row_ix: Option<usize>,\n        col_ix: usize,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Div {\n        let el = h_flex().h_full();\n        let selectable = self.col_selectable\n            && self\n                .col_groups\n                .get(col_ix)\n                .map(|col_group| col_group.column.selectable)\n                .unwrap_or(false);\n\n        // Don't show column selection if a cell is selected\n        if self.selection_mode.is_cell() {\n            return el;\n        }\n\n        if selectable && self.selected_col == Some(col_ix) && self.selection_mode.is_column() {\n            el.bg(cx.theme().table_active)\n        } else {\n            el\n        }\n    }\n\n    fn render_resize_handle(\n        &self,\n        ix: usize,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> impl IntoElement {\n        const HANDLE_SIZE: Pixels = px(2.);\n\n        let resizable = self.col_resizable\n            && self.col_groups.get(ix).map(|col| col.is_resizable()).unwrap_or(false);\n        if !resizable {\n            return div().into_any_element();\n        }\n\n        let group_id = SharedString::from(format!(\"resizable-handle:{}\", ix));\n\n        h_flex()\n            .id((\"resizable-handle\", ix))\n            .group(group_id.clone())\n            .occlude()\n            .cursor_col_resize()\n            .h_full()\n            .w(HANDLE_SIZE)\n            .ml(-(HANDLE_SIZE))\n            .justify_end()\n            .items_center()\n            .child(\n                div()\n                    .h_full()\n                    .justify_center()\n                    .bg(cx.theme().table_row_border)\n                    .group_hover(&group_id, |this| this.bg(cx.theme().border).h_full())\n                    .w(px(1.)),\n            )\n            .on_drag_move(cx.listener(move |view, e: &DragMoveEvent<ResizeColumn>, window, cx| {\n                match e.drag(cx) {\n                    ResizeColumn((entity_id, ix)) => {\n                        if cx.entity_id() != *entity_id {\n                            return;\n                        }\n\n                        // sync col widths into real widths\n                        // TODO: Consider to remove this, this may not need now.\n                        // for (_, col_group) in view.col_groups.iter_mut().enumerate() {\n                        //     col_group.width = col_group.bounds.size.width;\n                        // }\n\n                        let ix = *ix;\n                        view.resizing_col = Some(ix);\n\n                        let col_group =\n                            view.col_groups.get(ix).expect(\"BUG: invalid col index\").clone();\n\n                        view.resize_cols(\n                            ix,\n                            e.event.position.x - HANDLE_SIZE - col_group.bounds.left(),\n                            window,\n                            cx,\n                        );\n\n                        // scroll the table if the drag is near the edge\n                        view.scroll_table_by_col_resizing(e.event.position, &col_group);\n                    }\n                };\n            }))\n            .on_drag(ResizeColumn((cx.entity_id(), ix)), |drag, _, _, cx| {\n                cx.stop_propagation();\n                cx.new(|_| drag.clone())\n            })\n            .on_mouse_up_out(\n                MouseButton::Left,\n                cx.listener(|view, _, _, cx| {\n                    if view.resizing_col.is_none() {\n                        return;\n                    }\n\n                    view.resizing_col = None;\n\n                    let new_widths = view.col_groups.iter().map(|g| g.width).collect();\n                    cx.emit(TableEvent::ColumnWidthsChanged(new_widths));\n                    cx.notify();\n                }),\n            )\n            .into_any_element()\n    }\n\n    /// Render the row selector cell (when cell_selectable is enabled)\n    fn render_row_selector_cell(\n        &self,\n        row_ix: usize,\n        is_head: bool,\n        cx: &mut Context<Self>,\n    ) -> impl IntoElement {\n        div()\n            .id((\"row-selector\", row_ix))\n            .w_3()\n            .h_full()\n            .border_r_1()\n            .border_color(cx.theme().table_row_border)\n            .bg(cx.theme().table_head)\n            .flex_shrink_0()\n            .table_cell_size(self.options.size)\n            .when(!is_head, |this| {\n                this.when(self.row_selectable, |this| {\n                    this.on_click(cx.listener(move |table, _, _window, cx| {\n                        table.set_selected_row(row_ix, cx);\n                    }))\n                })\n            })\n    }\n\n    fn render_sort_icon(\n        &self,\n        col_ix: usize,\n        col_group: &ColGroup,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Option<impl IntoElement> {\n        if !self.sortable {\n            return None;\n        }\n\n        let Some(sort) = col_group.column.sort else {\n            return None;\n        };\n\n        let (icon, is_on) = match sort {\n            ColumnSort::Ascending => (IconName::SortAscending, true),\n            ColumnSort::Descending => (IconName::SortDescending, true),\n            ColumnSort::Default => (IconName::ChevronsUpDown, false),\n        };\n\n        Some(\n            div()\n                .id((\"icon-sort\", col_ix))\n                .p(px(2.))\n                .rounded(cx.theme().radius / 2.)\n                .map(|this| match is_on {\n                    true => this,\n                    false => this.opacity(0.5),\n                })\n                .hover(|this| this.bg(cx.theme().secondary).opacity(7.))\n                .active(|this| this.bg(cx.theme().secondary_active).opacity(1.))\n                .on_click(\n                    cx.listener(move |table, _, window, cx| table.perform_sort(col_ix, window, cx)),\n                )\n                .child(Icon::new(icon).size_3().text_color(cx.theme().secondary_foreground)),\n        )\n    }\n\n    /// Render the column header.\n    /// The children must be one by one items.\n    /// Because the horizontal scroll handle will use the child_item_bounds to\n    /// calculate the item position for itself's `scroll_to_item` method.\n    fn render_th(&mut self, col_ix: usize, window: &mut Window, cx: &mut Context<Self>) -> Div {\n        let entity_id = cx.entity_id();\n        let col_group = self.col_groups.get(col_ix).expect(\"BUG: invalid col index\");\n\n        let movable = self.col_movable && col_group.column.movable;\n        let paddings = col_group.column.paddings;\n        let name = col_group.column.name.clone();\n\n        h_flex()\n            .h_full()\n            .child(\n                self.render_cell(None, col_ix, window, cx)\n                    .id((\"col-header\", col_ix))\n                    .on_click(cx.listener(move |this, _, window, cx| {\n                        this.on_col_head_click(col_ix, window, cx);\n                    }))\n                    .child(\n                        h_flex()\n                            .size_full()\n                            .justify_between()\n                            .items_center()\n                            .child(self.delegate.render_th(col_ix, window, cx))\n                            .when_some(paddings, |this, paddings| {\n                                // Leave right space for the sort icon, if this column have custom padding\n                                let offset_pr =\n                                    self.options.size.table_cell_padding().right - paddings.right;\n                                this.pr(offset_pr.max(px(0.)))\n                            })\n                            .children(self.render_sort_icon(col_ix, &col_group, window, cx)),\n                    )\n                    .when(movable, |this| {\n                        this.on_drag(\n                            DragColumn { entity_id, col_ix, name, width: col_group.width },\n                            |drag, _, _, cx| {\n                                cx.stop_propagation();\n                                cx.new(|_| drag.clone())\n                            },\n                        )\n                        .drag_over::<DragColumn>(|this, _, _, cx| {\n                            this.rounded_l_none()\n                                .border_l_2()\n                                .border_r_0()\n                                .border_color(cx.theme().drag_border)\n                        })\n                        .on_drop(cx.listener(\n                            move |table, drag: &DragColumn, window, cx| {\n                                // If the drag col is not the same as the drop col, then swap the cols.\n                                if drag.entity_id != cx.entity_id() {\n                                    return;\n                                }\n\n                                table.move_column(drag.col_ix, col_ix, window, cx);\n                            },\n                        ))\n                    }),\n            )\n            // resize handle\n            .child(self.render_resize_handle(col_ix, window, cx))\n            // to save the bounds of this col.\n            .on_prepaint({\n                let view = cx.entity().clone();\n                move |bounds, _, cx| view.update(cx, |r, _| r.col_groups[col_ix].bounds = bounds)\n            })\n    }\n\n    fn render_table_header(\n        &mut self,\n        left_columns_count: usize,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> impl IntoElement {\n        let view = cx.entity().clone();\n        let horizontal_scroll_handle = self.horizontal_scroll_handle.clone();\n\n        // Reset fixed head columns bounds, if no fixed columns are present\n        if left_columns_count == 0 {\n            self.fixed_head_cols_bounds = Bounds::default();\n        }\n\n        let mut header = self.delegate_mut().render_header(window, cx);\n        let style = header.style().clone();\n\n        header\n            .h_flex()\n            .w_full()\n            .h(self.options.size.table_row_height())\n            .flex_shrink_0()\n            .border_b_1()\n            .border_color(cx.theme().border)\n            .text_color(cx.theme().table_head_foreground)\n            .refine_style(&style)\n            .when(self.cell_selectable, |this| {\n                this.child(self.render_row_selector_cell(0, true, cx))\n            })\n            .when(left_columns_count > 0, |this| {\n                let view = view.clone();\n                // Render left fixed columns\n                this.child(\n                    h_flex()\n                        .relative()\n                        .h_full()\n                        .bg(cx.theme().table_head)\n                        .children(\n                            self.col_groups\n                                .clone()\n                                .into_iter()\n                                .filter(|col| col.column.fixed == Some(ColumnFixed::Left))\n                                .enumerate()\n                                .map(|(col_ix, _)| self.render_th(col_ix, window, cx)),\n                        )\n                        .child(\n                            // Fixed columns border\n                            div()\n                                .absolute()\n                                .top_0()\n                                .right_0()\n                                .bottom_0()\n                                .w_0()\n                                .flex_shrink_0()\n                                .border_r_1()\n                                .border_color(cx.theme().border),\n                        )\n                        .on_prepaint(move |bounds, _, cx| {\n                            view.update(cx, |r, _| r.fixed_head_cols_bounds = bounds)\n                        }),\n                )\n            })\n            .child(\n                // Columns\n                h_flex()\n                    .id(\"table-head\")\n                    .size_full()\n                    .overflow_scroll()\n                    .relative()\n                    .track_scroll(&horizontal_scroll_handle)\n                    .bg(cx.theme().table_head)\n                    .child(\n                        h_flex()\n                            .relative()\n                            .children(\n                                self.col_groups\n                                    .clone()\n                                    .into_iter()\n                                    .skip(left_columns_count)\n                                    .enumerate()\n                                    .map(|(col_ix, _)| {\n                                        self.render_th(left_columns_count + col_ix, window, cx)\n                                    }),\n                            )\n                            .child(self.delegate.render_last_empty_col(window, cx)),\n                    ),\n            )\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    fn render_table_row(\n        &mut self,\n        row_ix: usize,\n        rows_count: usize,\n        left_columns_count: usize,\n        col_sizes: Rc<Vec<gpui::Size<Pixels>>>,\n        columns_count: usize,\n        is_filled: bool,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> Stateful<Div> {\n        let horizontal_scroll_handle = self.horizontal_scroll_handle.clone();\n        let is_stripe_row = self.options.stripe && row_ix % 2 != 0;\n        let is_selected = self.selected_row == Some(row_ix);\n        let view = cx.entity().clone();\n        let row_height = self.options.size.table_row_height();\n\n        if row_ix < rows_count {\n            let is_last_row = row_ix + 1 == rows_count;\n            let need_render_border = is_selected || !is_last_row || !is_filled;\n\n            let mut tr = self.delegate.render_tr(row_ix, window, cx);\n            let style = tr.style().clone();\n\n            tr.h_flex()\n                .w_full()\n                .h(row_height)\n                .when(need_render_border, |this| {\n                    this.border_b_1().border_color(cx.theme().table_row_border)\n                })\n                .when(is_stripe_row, |this| this.bg(cx.theme().table_even))\n                .refine_style(&style)\n                .hover(|this| {\n                    if is_selected || self.right_clicked_row == Some(row_ix) {\n                        this\n                    } else {\n                        this.bg(cx.theme().table_hover)\n                    }\n                })\n                .when(self.cell_selectable, |this| {\n                    this.child(self.render_row_selector_cell(row_ix, false, cx))\n                })\n                .when(left_columns_count > 0, |this| {\n                    // Left fixed columns\n                    this.child(\n                        h_flex()\n                            .relative()\n                            .h_full()\n                            .children({\n                                let mut items = Vec::with_capacity(left_columns_count);\n\n                                (0..left_columns_count).for_each(|col_ix| {\n                                    let is_cell_selected = self.selected_cell\n                                        == Some((row_ix, col_ix))\n                                        && self.selection_mode.is_cell();\n                                    let is_cell_right_clicked =\n                                        self.right_clicked_cell == Some((row_ix, col_ix));\n\n                                    items.push(\n                                        self.render_col_wrap(Some(row_ix), col_ix, window, cx)\n                                            .child(\n                                                self.render_cell(Some(row_ix), col_ix, window, cx)\n                                                    .id(format!(\"table-cell:{}:{}\", row_ix, col_ix))\n                                                    .relative()\n                                                    .child(self.measure_render_td(\n                                                        row_ix, col_ix, window, cx,\n                                                    ))\n                                                    .when(is_cell_selected, |this| {\n                                                        this.child(\n                                                            div()\n                                                                .absolute()\n                                                                .inset_0()\n                                                                .bg(cx.theme().table_active)\n                                                                .border_1()\n                                                                .border_color(\n                                                                    cx.theme().table_active_border,\n                                                                ),\n                                                        )\n                                                    })\n                                                    .when(\n                                                        is_cell_right_clicked && !is_cell_selected,\n                                                        |this| {\n                                                            this.child(\n                                                                div()\n                                                                    .absolute()\n                                                                    .inset_0()\n                                                                    .border_1()\n                                                                    .border_color(\n                                                                        cx.theme()\n                                                                            .table_active_border\n                                                                            .opacity(0.5),\n                                                                    ),\n                                                            )\n                                                        },\n                                                    )\n                                                    .when(self.cell_selectable, |this| {\n                                                        this.on_click(cx.listener(\n                                                            move |table, e, window, cx| {\n                                                                table.on_cell_click(\n                                                                    e, row_ix, col_ix, window, cx,\n                                                                );\n                                                            },\n                                                        ))\n                                                        .on_mouse_down(\n                                                            MouseButton::Right,\n                                                            cx.listener(\n                                                                move |table, e, window, cx| {\n                                                                    table.on_cell_right_click(\n                                                                        e, row_ix, col_ix, window,\n                                                                        cx,\n                                                                    );\n                                                                },\n                                                            ),\n                                                        )\n                                                    }),\n                                            ),\n                                    );\n                                });\n\n                                items\n                            })\n                            .child(\n                                // Fixed columns border\n                                div()\n                                    .absolute()\n                                    .top_0()\n                                    .right_0()\n                                    .bottom_0()\n                                    .w_0()\n                                    .flex_shrink_0()\n                                    .border_r_1()\n                                    .border_color(cx.theme().border),\n                            ),\n                    )\n                })\n                .child(\n                    h_flex()\n                        .flex_1()\n                        .h_full()\n                        .overflow_hidden()\n                        .relative()\n                        .child(\n                            crate::virtual_list::virtual_list(\n                                view,\n                                row_ix,\n                                Axis::Horizontal,\n                                col_sizes,\n                                {\n                                    move |table, visible_range: Range<usize>, window, cx| {\n                                        table.update_visible_range_if_need(\n                                            visible_range.clone(),\n                                            Axis::Horizontal,\n                                            window,\n                                            cx,\n                                        );\n\n                                        let mut items = Vec::with_capacity(\n                                            visible_range.end - visible_range.start,\n                                        );\n\n                                        visible_range.for_each(|col_ix| {\n                                            let col_ix = col_ix + left_columns_count;\n                                            let is_cell_selected = table.selected_cell\n                                                == Some((row_ix, col_ix))\n                                                && table.selection_mode.is_cell();\n                                            let is_cell_right_clicked =\n                                                table.right_clicked_cell == Some((row_ix, col_ix));\n\n                                            let el = table\n                                                .render_col_wrap(Some(row_ix), col_ix, window, cx)\n                                                .child(\n                                                    table\n                                                        .render_cell(\n                                                            Some(row_ix),\n                                                            col_ix,\n                                                            window,\n                                                            cx,\n                                                        )\n                                                        .id(format!(\n                                                            \"table-cell-{}:{}\",\n                                                            row_ix, col_ix\n                                                        ))\n                                                        .relative()\n                                                        .child(table.measure_render_td(\n                                                            row_ix, col_ix, window, cx,\n                                                        ))\n                                                        .when(is_cell_selected, |this| {\n                                                            this.child(\n                                                                div()\n                                                                    .absolute()\n                                                                    .inset_0()\n                                                                    .bg(cx.theme().table_active)\n                                                                    .border_1()\n                                                                    .border_color(\n                                                                        cx.theme()\n                                                                            .table_active_border,\n                                                                    ),\n                                                            )\n                                                        })\n                                                        .when(\n                                                            is_cell_right_clicked\n                                                                && !is_cell_selected,\n                                                            |this| {\n                                                                this.child(\n                                                                    div()\n                                                                        .absolute()\n                                                                        .inset_0()\n                                                                        .border_1()\n                                                                        .border_color(\n                                                                            cx.theme()\n                                                                                .table_active_border\n                                                                                .opacity(0.5),\n                                                                        ),\n                                                                )\n                                                            },\n                                                        )\n                                                        .when(table.cell_selectable, |this| {\n                                                            this.on_click(cx.listener(\n                                                                move |table, e, window, cx| {\n                                                                    cx.stop_propagation();\n                                                                    table.on_cell_click(\n                                                                        e, row_ix, col_ix, window,\n                                                                        cx,\n                                                                    );\n                                                                },\n                                                            ))\n                                                            .on_mouse_down(\n                                                                MouseButton::Right,\n                                                                cx.listener(\n                                                                    move |table, e, window, cx| {\n                                                                        table.on_cell_right_click(\n                                                                            e, row_ix, col_ix,\n                                                                            window, cx,\n                                                                        );\n                                                                    },\n                                                                ),\n                                                            )\n                                                        }),\n                                                );\n\n                                            items.push(el);\n                                        });\n\n                                        items\n                                    }\n                                },\n                            )\n                            .with_scroll_handle(&self.horizontal_scroll_handle),\n                        )\n                        .child(self.delegate.render_last_empty_col(window, cx)),\n                )\n                // Row selected style\n                // Note: Don't show row selection if a cell is selected\n                .when_some(self.selected_row, |this, _| {\n                    this.when(is_selected && self.selection_mode.is_row(), |this| {\n                        this.map(|this| {\n                            if cx.theme().list.active_highlight {\n                                this.border_color(gpui::transparent_white()).child(\n                                    div()\n                                        .top(if row_ix == 0 { px(0.) } else { px(-1.) })\n                                        .left(px(0.))\n                                        .right(px(0.))\n                                        .bottom(px(-1.))\n                                        .absolute()\n                                        .bg(cx.theme().table_active)\n                                        .border_1()\n                                        .border_color(cx.theme().table_active_border),\n                                )\n                            } else {\n                                this.bg(cx.theme().accent)\n                            }\n                        })\n                    })\n                })\n                // Row right click row style\n                .when(self.right_clicked_row == Some(row_ix), |this| {\n                    this.border_color(gpui::transparent_white()).child(\n                        div()\n                            .top(if row_ix == 0 { px(0.) } else { px(-1.) })\n                            .left(px(0.))\n                            .right(px(0.))\n                            .bottom(px(-1.))\n                            .absolute()\n                            .border_1()\n                            .border_color(cx.theme().selection),\n                    )\n                })\n                .on_mouse_down(\n                    MouseButton::Right,\n                    cx.listener(move |this, e, window, cx| {\n                        this.on_row_right_click(e, Some(row_ix), window, cx);\n                    }),\n                )\n                .on_click(cx.listener(move |this, e, window, cx| {\n                    this.on_row_left_click(e, row_ix, window, cx);\n                }))\n        } else {\n            // Render fake rows to fill the rest table space\n            self.delegate\n                .render_tr(row_ix, window, cx)\n                .h_flex()\n                .w_full()\n                .h(row_height)\n                .border_b_1()\n                .border_color(cx.theme().table_row_border)\n                .when(is_stripe_row, |this| this.bg(cx.theme().table_even))\n                .when(self.cell_selectable, |this| {\n                    // Render empty row selector cell for fake rows\n                    this.child(\n                        div()\n                            .w(px(40.))\n                            .h_full()\n                            .flex_shrink_0()\n                            .table_cell_size(self.options.size),\n                    )\n                })\n                .children((0..columns_count).map(|col_ix| {\n                    h_flex()\n                        .left(horizontal_scroll_handle.offset().x)\n                        .child(self.render_cell(None, col_ix, window, cx))\n                }))\n                .child(self.delegate.render_last_empty_col(window, cx))\n        }\n    }\n\n    /// Calculate the extra rows needed to fill the table empty space when `stripe` is true.\n    fn calculate_extra_rows_needed(\n        &self,\n        total_height: Pixels,\n        actual_height: Pixels,\n        row_height: Pixels,\n    ) -> usize {\n        let mut extra_rows_needed = 0;\n\n        let remaining_height = total_height - actual_height;\n        if remaining_height > px(0.) {\n            extra_rows_needed = (remaining_height / row_height).floor() as usize;\n        }\n\n        extra_rows_needed\n    }\n\n    #[inline]\n    fn measure_render_td(\n        &mut self,\n        row_ix: usize,\n        col_ix: usize,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) -> impl IntoElement {\n        if !crate::measure_enable() {\n            return self.delegate.render_td(row_ix, col_ix, window, cx).into_any_element();\n        }\n\n        let start = std::time::Instant::now();\n        let el = self.delegate.render_td(row_ix, col_ix, window, cx);\n        self._measure.push(start.elapsed());\n        el.into_any_element()\n    }\n\n    fn measure(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {\n        if !crate::measure_enable() {\n            return;\n        }\n\n        // Print avg measure time of each td\n        if self._measure.len() > 0 {\n            let total = self._measure.iter().fold(Duration::default(), |acc, d| acc + *d);\n            let avg = total / self._measure.len() as u32;\n            eprintln!(\n                \"last render {} cells total: {:?}, avg: {:?}\",\n                self._measure.len(),\n                total,\n                avg,\n            );\n        }\n        self._measure.clear();\n    }\n\n    fn render_vertical_scrollbar(\n        &mut self,\n\n        _: &mut Window,\n        _: &mut Context<Self>,\n    ) -> Option<impl IntoElement> {\n        Some(\n            div()\n                .absolute()\n                .top(self.options.size.table_row_height())\n                .right_0()\n                .bottom_0()\n                .w(Scrollbar::width())\n                .child(Scrollbar::vertical(&self.vertical_scroll_handle).max_fps(60)),\n        )\n    }\n\n    fn render_horizontal_scrollbar(\n        &mut self,\n        _: &mut Window,\n        _: &mut Context<Self>,\n    ) -> impl IntoElement {\n        div()\n            .absolute()\n            .left(self.fixed_head_cols_bounds.size.width)\n            .right_0()\n            .bottom_0()\n            .h(Scrollbar::width())\n            .child(Scrollbar::horizontal(&self.horizontal_scroll_handle))\n    }\n}\n\nimpl<D> Focusable for TableState<D>\nwhere\n    D: TableDelegate,\n{\n    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\nimpl<D> EventEmitter<TableEvent> for TableState<D> where D: TableDelegate {}\n\nimpl<D> Render for TableState<D>\nwhere\n    D: TableDelegate,\n{\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        self.measure(window, cx);\n\n        let columns_count = self.delegate.columns_count(cx);\n        let left_columns_count = self\n            .col_groups\n            .iter()\n            .filter(|col| self.col_fixed && col.column.fixed == Some(ColumnFixed::Left))\n            .count();\n        let rows_count = self.delegate.rows_count(cx);\n        let loading = self.delegate.loading(cx);\n\n        let row_height = self.options.size.table_row_height();\n        let total_height = self.vertical_scroll_handle.0.borrow().base_handle.bounds().size.height;\n        let actual_height = row_height * rows_count as f32;\n        let extra_rows_count =\n            self.calculate_extra_rows_needed(total_height, actual_height, row_height);\n        let render_rows_count =\n            if self.options.stripe { rows_count + extra_rows_count } else { rows_count };\n        let right_clicked_row = self.right_clicked_row;\n        let is_filled = total_height > Pixels::ZERO && total_height <= actual_height;\n\n        let loading_view = if loading {\n            Some(self.delegate.render_loading(self.options.size, window, cx).into_any_element())\n        } else {\n            None\n        };\n\n        let empty_view = if rows_count == 0 {\n            Some(div().size_full().child(self.delegate.render_empty(window, cx)).into_any_element())\n        } else {\n            None\n        };\n\n        let inner_table = v_flex()\n            .id(\"table-inner\")\n            .size_full()\n            .overflow_hidden()\n            .child(self.render_table_header(left_columns_count, window, cx))\n            .context_menu({\n                let view = cx.entity().clone();\n                move |this, window: &mut Window, cx: &mut Context<PopupMenu>| {\n                    if let Some(row_ix) = view.read(cx).right_clicked_row {\n                        view.update(cx, |menu, cx| {\n                            menu.delegate_mut().context_menu(row_ix, this, window, cx)\n                        })\n                    } else {\n                        this\n                    }\n                }\n            })\n            .map(|this| {\n                if rows_count == 0 {\n                    this.children(empty_view)\n                } else {\n                    this.child(\n                        h_flex().id(\"table-body\").flex_grow().size_full().child(\n                            uniform_list(\n                                \"table-uniform-list\",\n                                render_rows_count,\n                                cx.processor(\n                                    move |table, visible_range: Range<usize>, window, cx| {\n                                        // We must calculate the col sizes here, because the col sizes\n                                        // need render_th first, then that method will set the bounds of each col.\n                                        let col_sizes: Rc<Vec<gpui::Size<Pixels>>> = Rc::new(\n                                            table\n                                                .col_groups\n                                                .iter()\n                                                .skip(left_columns_count)\n                                                .map(|col| col.bounds.size)\n                                                .collect(),\n                                        );\n\n                                        table.load_more_if_need(\n                                            rows_count,\n                                            visible_range.end,\n                                            window,\n                                            cx,\n                                        );\n                                        table.update_visible_range_if_need(\n                                            visible_range.clone(),\n                                            Axis::Vertical,\n                                            window,\n                                            cx,\n                                        );\n\n                                        if visible_range.end > rows_count {\n                                            table.scroll_to_row(\n                                                std::cmp::min(\n                                                    visible_range.start,\n                                                    rows_count.saturating_sub(1),\n                                                ),\n                                                cx,\n                                            );\n                                        }\n\n                                        let mut items = Vec::with_capacity(\n                                            visible_range.end.saturating_sub(visible_range.start),\n                                        );\n\n                                        // Render fake rows to fill the table\n                                        visible_range.for_each(|row_ix| {\n                                            // Render real rows for available data\n                                            items.push(table.render_table_row(\n                                                row_ix,\n                                                rows_count,\n                                                left_columns_count,\n                                                col_sizes.clone(),\n                                                columns_count,\n                                                is_filled,\n                                                window,\n                                                cx,\n                                            ));\n                                        });\n\n                                        items\n                                    },\n                                ),\n                            )\n                            .flex_grow()\n                            .size_full()\n                            .with_sizing_behavior(ListSizingBehavior::Auto)\n                            .track_scroll(&self.vertical_scroll_handle)\n                            .into_any_element(),\n                        ),\n                    )\n                }\n            });\n\n        div()\n            .size_full()\n            .children(loading_view)\n            .when(!loading, |this| {\n                this.child(inner_table)\n                    .child(ScrollableMask::new(Axis::Horizontal, &self.horizontal_scroll_handle))\n                    .when(right_clicked_row.is_some(), |this| {\n                        this.on_mouse_down_out(cx.listener(|this, e, window, cx| {\n                            this.on_row_right_click(e, None, window, cx);\n                            cx.notify();\n                        }))\n                    })\n            })\n            .on_prepaint({\n                let state = cx.entity();\n                move |bounds, _, cx| state.update(cx, |state, _| state.bounds = bounds)\n            })\n            .when(!window.is_inspector_picking(cx), |this| {\n                this.child(\n                    div()\n                        .absolute()\n                        .top_0()\n                        .size_full()\n                        .when(self.options.scrollbar_visible.bottom, |this| {\n                            this.child(self.render_horizontal_scrollbar(window, cx))\n                        })\n                        .when(self.options.scrollbar_visible.right && rows_count > 0, |this| {\n                            this.children(self.render_vertical_scrollbar(window, cx))\n                        }),\n                )\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/table/table.rs",
    "content": "use gpui::{\n    AnyElement, App, InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce,\n    StyleRefinement, Styled, TextAlign, Window, div, prelude::FluentBuilder as _, px, relative,\n};\n\nuse crate::{ActiveTheme as _, AnyChildElement, ChildElement, Sizable, Size, StyledExt as _};\n\nconst MIN_CELL_WIDTH: Pixels = px(100.);\n\n/// A basic table component for directly rendering tabular data.\n///\n/// Unlike [`DataTable`], this is a simple, stateless, composable table\n/// without virtual scrolling or column management.\n///\n/// Size set via [`Sizable`] is automatically propagated to all children.\n///\n/// # Example\n///\n/// ```rust,ignore\n/// Table::new()\n///     .small()\n///     .child(TableHeader::new().child(\n///         TableRow::new()\n///             .child(TableHead::new().child(\"Name\"))\n///             .child(TableHead::new().child(\"Email\"))\n///     ))\n///     .child(TableBody::new()\n///         .child(TableRow::new()\n///             .child(TableCell::new().child(\"John\"))\n///             .child(TableCell::new().child(\"john@example.com\")))\n///     )\n///     .child(TableCaption::new().child(\"A list of recent invoices.\"))\n/// ```\n#[derive(IntoElement)]\npub struct Table {\n    ix: usize,\n    style: StyleRefinement,\n    children: Vec<AnyChildElement>,\n    size: Size,\n}\n\nimpl Table {\n    pub fn new() -> Self {\n        Self {\n            ix: 0,\n            style: StyleRefinement::default(),\n            children: Vec::new(),\n            size: Size::default(),\n        }\n    }\n\n    pub fn child(mut self, child: impl ChildElement + 'static) -> Self {\n        self.children.push(AnyChildElement::new(child));\n        self\n    }\n\n    pub fn children<E: ChildElement + 'static>(\n        mut self,\n        children: impl IntoIterator<Item = E>,\n    ) -> Self {\n        self.children.extend(children.into_iter().map(AnyChildElement::new));\n        self\n    }\n}\n\nimpl Styled for Table {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Sizable for Table {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl ChildElement for Table {\n    fn with_ix(mut self, ix: usize) -> Self {\n        self.ix = ix;\n        self\n    }\n}\n\nimpl RenderOnce for Table {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        div()\n            .id((\"table\", self.ix))\n            .w_full()\n            .text_sm()\n            .overflow_hidden()\n            .bg(cx.theme().table)\n            .refine_style(&self.style)\n            .children(\n                self.children.into_iter().enumerate().map(|(ix, c)| c.into_any(ix, self.size)),\n            )\n    }\n}\n\n/// The header section of a [`Table`], wrapping header rows.\n#[derive(IntoElement)]\npub struct TableHeader {\n    ix: usize,\n    style: StyleRefinement,\n    children: Vec<AnyChildElement>,\n    size: Size,\n}\n\nimpl TableHeader {\n    pub fn new() -> Self {\n        Self {\n            ix: 0,\n            style: StyleRefinement::default(),\n            children: Vec::new(),\n            size: Size::default(),\n        }\n    }\n\n    pub fn child(mut self, child: impl ChildElement + 'static) -> Self {\n        self.children.push(AnyChildElement::new(child));\n        self\n    }\n\n    pub fn children<E: ChildElement + 'static>(\n        mut self,\n        children: impl IntoIterator<Item = E>,\n    ) -> Self {\n        self.children.extend(children.into_iter().map(AnyChildElement::new));\n        self\n    }\n}\n\nimpl Styled for TableHeader {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl ChildElement for TableHeader {\n    fn with_ix(mut self, ix: usize) -> Self {\n        self.ix = ix;\n        self\n    }\n}\n\nimpl Sizable for TableHeader {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl RenderOnce for TableHeader {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        div()\n            .id((\"table-header\", self.ix))\n            .w_full()\n            .bg(cx.theme().table_head)\n            .text_color(cx.theme().table_head_foreground)\n            .refine_style(&self.style)\n            .border_b_1()\n            .border_color(cx.theme().table_row_border)\n            .children(\n                self.children.into_iter().enumerate().map(|(ix, c)| c.into_any(ix, self.size)),\n            )\n    }\n}\n\n/// The body section of a [`Table`], wrapping data rows.\n#[derive(IntoElement)]\npub struct TableBody {\n    ix: usize,\n    style: StyleRefinement,\n    children: Vec<AnyChildElement>,\n    size: Size,\n}\n\nimpl TableBody {\n    pub fn new() -> Self {\n        Self {\n            ix: 0,\n            style: StyleRefinement::default(),\n            children: Vec::new(),\n            size: Size::default(),\n        }\n    }\n\n    pub fn child(mut self, child: impl ChildElement + 'static) -> Self {\n        self.children.push(AnyChildElement::new(child));\n        self\n    }\n\n    pub fn children<E: ChildElement + 'static>(\n        mut self,\n        children: impl IntoIterator<Item = E>,\n    ) -> Self {\n        self.children.extend(children.into_iter().map(AnyChildElement::new));\n        self\n    }\n}\n\nimpl Styled for TableBody {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Sizable for TableBody {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl ChildElement for TableBody {\n    fn with_ix(mut self, ix: usize) -> Self {\n        self.ix = ix;\n        self\n    }\n}\n\nimpl RenderOnce for TableBody {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        div().id((\"table-body\", self.ix)).w_full().refine_style(&self.style).children(\n            self.children.into_iter().enumerate().map(|(ix, c)| c.into_any(ix, self.size)),\n        )\n    }\n}\n\n/// The footer section of a [`Table`], wrapping footer rows.\n#[derive(IntoElement)]\npub struct TableFooter {\n    ix: usize,\n    style: StyleRefinement,\n    children: Vec<AnyChildElement>,\n    size: Size,\n}\n\nimpl TableFooter {\n    pub fn new() -> Self {\n        Self {\n            ix: 0,\n            style: StyleRefinement::default(),\n            children: Vec::new(),\n            size: Size::default(),\n        }\n    }\n\n    pub fn child(mut self, child: impl ChildElement + 'static) -> Self {\n        self.children.push(AnyChildElement::new(child));\n        self\n    }\n\n    pub fn children<E: ChildElement + 'static>(\n        mut self,\n        children: impl IntoIterator<Item = E>,\n    ) -> Self {\n        self.children.extend(children.into_iter().map(AnyChildElement::new));\n        self\n    }\n}\n\nimpl Styled for TableFooter {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Sizable for TableFooter {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl ChildElement for TableFooter {\n    fn with_ix(mut self, ix: usize) -> Self {\n        self.ix = ix;\n        self\n    }\n}\n\nimpl RenderOnce for TableFooter {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        div()\n            .id((\"table-footer\", self.ix))\n            .w_full()\n            .bg(cx.theme().table_foot)\n            .text_color(cx.theme().table_foot_foreground)\n            .border_t_1()\n            .border_color(cx.theme().table_row_border)\n            .refine_style(&self.style)\n            .children(\n                self.children.into_iter().enumerate().map(|(ix, c)| c.into_any(ix, self.size)),\n            )\n    }\n}\n\n/// A row in a [`Table`].\n#[derive(IntoElement)]\npub struct TableRow {\n    ix: usize,\n    style: StyleRefinement,\n    children: Vec<AnyChildElement>,\n    size: Size,\n}\n\nimpl TableRow {\n    pub fn new() -> Self {\n        Self {\n            ix: 0,\n            style: StyleRefinement::default(),\n            children: Vec::new(),\n            size: Size::default(),\n        }\n    }\n\n    pub fn child(mut self, child: impl ChildElement + 'static) -> Self {\n        self.children.push(AnyChildElement::new(child));\n        self\n    }\n\n    pub fn children<E: ChildElement + 'static>(\n        mut self,\n        children: impl IntoIterator<Item = E>,\n    ) -> Self {\n        self.children.extend(children.into_iter().map(AnyChildElement::new));\n        self\n    }\n}\n\nimpl Styled for TableRow {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Sizable for TableRow {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl ChildElement for TableRow {\n    fn with_ix(mut self, ix: usize) -> Self {\n        self.ix = ix;\n        self\n    }\n}\n\nimpl RenderOnce for TableRow {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        div()\n            .id((\"table-row\", self.ix))\n            .w_full()\n            .flex()\n            .flex_row()\n            .refine_style(&self.style)\n            .border_color(cx.theme().table_row_border)\n            .when(self.ix > 0, |this| this.border_t_1())\n            .children(\n                self.children.into_iter().enumerate().map(|(ix, c)| c.into_any(ix, self.size)),\n            )\n    }\n}\n\n/// A header cell in a [`TableRow`].\n#[derive(IntoElement)]\npub struct TableHead {\n    ix: usize,\n    style: StyleRefinement,\n    children: Vec<AnyElement>,\n    col_span: usize,\n    align: TextAlign,\n    size: Size,\n}\n\nimpl TableHead {\n    pub fn new() -> Self {\n        Self {\n            ix: 0,\n            style: StyleRefinement::default(),\n            children: Vec::new(),\n            col_span: 1,\n            align: TextAlign::Left,\n            size: Size::default(),\n        }\n    }\n\n    /// Set the column span of this header cell.\n    pub fn col_span(mut self, span: usize) -> Self {\n        self.col_span = span.max(1);\n        self\n    }\n\n    /// Set text alignment to center.\n    pub fn text_center(mut self) -> Self {\n        self.align = TextAlign::Center;\n        self\n    }\n\n    /// Set text alignment to right.\n    pub fn text_right(mut self) -> Self {\n        self.align = TextAlign::Right;\n        self\n    }\n}\n\nimpl ParentElement for TableHead {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Sizable for TableHead {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl ChildElement for TableHead {\n    fn with_ix(mut self, ix: usize) -> Self {\n        self.ix = ix;\n        self\n    }\n}\n\nimpl Styled for TableHead {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for TableHead {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        let paddings = self.size.table_cell_padding();\n\n        div()\n            .id((\"table-head\", self.ix))\n            .flex()\n            .items_center()\n            .when(self.style.size.width.is_none(), |this| {\n                this.flex_shrink().flex_basis(relative(self.col_span as f32))\n            })\n            .min_w(MIN_CELL_WIDTH * self.col_span)\n            .px(paddings.left)\n            .py(paddings.top)\n            .when(self.align == TextAlign::Center, |this| this.justify_center())\n            .when(self.align == TextAlign::Right, |this| this.justify_end())\n            .refine_style(&self.style)\n            .children(self.children)\n    }\n}\n\n/// A data cell in a [`TableRow`].\n#[derive(IntoElement)]\npub struct TableCell {\n    ix: usize,\n    style: StyleRefinement,\n    children: Vec<AnyElement>,\n    col_span: usize,\n    align: TextAlign,\n    size: Size,\n}\n\nimpl TableCell {\n    pub fn new() -> Self {\n        Self {\n            ix: 0,\n            style: StyleRefinement::default(),\n            children: Vec::new(),\n            col_span: 1,\n            align: TextAlign::Left,\n            size: Size::default(),\n        }\n    }\n\n    /// Set the column span of this cell.\n    pub fn col_span(mut self, span: usize) -> Self {\n        self.col_span = span.max(1);\n        self\n    }\n\n    /// Set text alignment to center.\n    pub fn text_center(mut self) -> Self {\n        self.align = TextAlign::Center;\n        self\n    }\n\n    /// Set text alignment to right.\n    pub fn text_right(mut self) -> Self {\n        self.align = TextAlign::Right;\n        self\n    }\n}\n\nimpl ParentElement for TableCell {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Sizable for TableCell {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl ChildElement for TableCell {\n    fn with_ix(mut self, ix: usize) -> Self {\n        self.ix = ix;\n        self\n    }\n}\n\nimpl Styled for TableCell {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for TableCell {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        let paddings = self.size.table_cell_padding();\n\n        div()\n            .id((\"table-cell\", self.ix))\n            .flex()\n            .items_center()\n            .when(self.style.size.width.is_none(), |this| {\n                this.flex_shrink().flex_basis(relative(self.col_span as f32))\n            })\n            .min_w(MIN_CELL_WIDTH * self.col_span)\n            .px(paddings.left)\n            .py(paddings.top)\n            .when(self.align == TextAlign::Center, |this| this.justify_center())\n            .when(self.align == TextAlign::Right, |this| this.justify_end())\n            .refine_style(&self.style)\n            .children(self.children)\n    }\n}\n\n/// A caption displayed below the [`Table`].\n#[derive(IntoElement)]\npub struct TableCaption {\n    ix: usize,\n    style: StyleRefinement,\n    children: Vec<AnyElement>,\n    size: Size,\n}\n\nimpl TableCaption {\n    pub fn new() -> Self {\n        Self {\n            ix: 0,\n            style: StyleRefinement::default(),\n            children: Vec::new(),\n            size: Size::default(),\n        }\n    }\n}\n\nimpl ParentElement for TableCaption {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Sizable for TableCaption {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl ChildElement for TableCaption {\n    fn with_ix(mut self, ix: usize) -> Self {\n        self.ix = ix;\n        self\n    }\n}\n\nimpl Styled for TableCaption {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for TableCaption {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let paddings = self.size.table_cell_padding();\n\n        div()\n            .id((\"table-caption\", self.ix))\n            .w_full()\n            .px(paddings.left)\n            .py(paddings.top)\n            .text_sm()\n            .text_color(cx.theme().muted_foreground)\n            .text_center()\n            .refine_style(&self.style)\n            .children(self.children)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/tag.rs",
    "content": "use crate::{theme::ActiveTheme as _, ColorName, Sizable, Size, StyledExt};\nuse gpui::{\n    div, prelude::FluentBuilder as _, relative, rems, transparent_white, AbsoluteLength,\n    AnyElement, App, Hsla, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,\n    StyleRefinement, Styled, Window,\n};\n\n/// The variant of the Tag.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\npub enum TagVariant {\n    Primary,\n    #[default]\n    Secondary,\n    Danger,\n    Success,\n    Warning,\n    Info,\n    Color(ColorName),\n    Custom {\n        color: Hsla,\n        foreground: Hsla,\n        border: Hsla,\n    },\n}\n\nimpl TagVariant {\n    fn bg(&self, cx: &App) -> Hsla {\n        match self {\n            Self::Primary => cx.theme().primary,\n            Self::Secondary => cx.theme().secondary,\n            Self::Danger => cx.theme().danger,\n            Self::Success => cx.theme().success,\n            Self::Warning => cx.theme().warning,\n            Self::Info => cx.theme().info,\n            Self::Color(color) => {\n                if cx.theme().is_dark() {\n                    color.scale(950).opacity(0.5)\n                } else {\n                    color.scale(50)\n                }\n            }\n            Self::Custom { color, .. } => *color,\n        }\n    }\n\n    fn border(&self, cx: &App) -> Hsla {\n        match self {\n            Self::Primary => cx.theme().primary,\n            Self::Secondary => cx.theme().border,\n            Self::Danger => cx.theme().danger,\n            Self::Success => cx.theme().success,\n            Self::Warning => cx.theme().warning,\n            Self::Info => cx.theme().info,\n            Self::Color(color) => {\n                if cx.theme().is_dark() {\n                    color.scale(800).opacity(0.5)\n                } else {\n                    color.scale(200)\n                }\n            }\n            Self::Custom { border, .. } => *border,\n        }\n    }\n\n    fn fg(&self, outline: bool, cx: &App) -> Hsla {\n        match self {\n            Self::Primary => {\n                if outline {\n                    cx.theme().primary\n                } else {\n                    cx.theme().primary_foreground\n                }\n            }\n            Self::Secondary => {\n                if outline {\n                    cx.theme().muted_foreground\n                } else {\n                    cx.theme().secondary_foreground\n                }\n            }\n            Self::Danger => {\n                if outline {\n                    cx.theme().danger\n                } else {\n                    cx.theme().danger_foreground\n                }\n            }\n            Self::Success => {\n                if outline {\n                    cx.theme().success\n                } else {\n                    cx.theme().success_foreground\n                }\n            }\n            Self::Warning => {\n                if outline {\n                    cx.theme().warning\n                } else {\n                    cx.theme().warning_foreground\n                }\n            }\n            Self::Info => {\n                if outline {\n                    cx.theme().info\n                } else {\n                    cx.theme().info_foreground\n                }\n            }\n            Self::Color(color) => {\n                if cx.theme().is_dark() {\n                    color.scale(300)\n                } else {\n                    color.scale(600)\n                }\n            }\n            Self::Custom { foreground, .. } => *foreground,\n        }\n    }\n}\n\n/// Tag is a small status indicator.\n///\n/// Only support: Medium, Small\n#[derive(IntoElement)]\npub struct Tag {\n    style: StyleRefinement,\n    variant: TagVariant,\n    outline: bool,\n    size: Size,\n    rounded: Option<AbsoluteLength>,\n    children: Vec<AnyElement>,\n}\nimpl Tag {\n    /// Create a new Tag.\n    pub fn new() -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            variant: TagVariant::default(),\n            outline: false,\n            size: Size::default(),\n            rounded: None,\n            children: Vec::new(),\n        }\n    }\n\n    /// Create a new tag with default variant ([`TagVariant::Primary`]).\n    pub fn primary() -> Self {\n        Self::new().with_variant(TagVariant::Primary)\n    }\n\n    /// Create a new tag with default variant ([`TagVariant::Secondary`]).\n    pub fn secondary() -> Self {\n        Self::new().with_variant(TagVariant::Secondary)\n    }\n\n    /// Create a new tag with default variant ([`TagVariant::Danger`]).\n    pub fn danger() -> Self {\n        Self::new().with_variant(TagVariant::Danger)\n    }\n\n    /// Create a new tag with default variant ([`TagVariant::Success`]).\n    pub fn success() -> Self {\n        Self::new().with_variant(TagVariant::Success)\n    }\n\n    /// Create a new tag with default variant ([`TagVariant::Warning`]).\n    pub fn warning() -> Self {\n        Self::new().with_variant(TagVariant::Warning)\n    }\n\n    /// Create a new tag with default variant ([`TagVariant::Info`]).\n    pub fn info() -> Self {\n        Self::new().with_variant(TagVariant::Info)\n    }\n\n    /// Create a new tag with default variant ([`TagVariant::Custom`]).\n    pub fn custom(color: Hsla, foreground: Hsla, border: Hsla) -> Self {\n        Self::new().with_variant(TagVariant::Custom {\n            color,\n            foreground,\n            border,\n        })\n    }\n\n    /// Create a new tag with default variant ([`TagVariant::Color`]).\n    pub fn color(color: impl Into<ColorName>) -> Self {\n        Self::new().with_variant(TagVariant::Color(color.into()))\n    }\n\n    /// Set the variant of the Tag.\n    pub fn with_variant(mut self, variant: TagVariant) -> Self {\n        self.variant = variant;\n        self\n    }\n\n    /// Use outline style\n    pub fn outline(mut self) -> Self {\n        self.outline = true;\n        self\n    }\n\n    /// Set rounded corners.\n    pub fn rounded(mut self, radius: impl Into<AbsoluteLength>) -> Self {\n        self.rounded = Some(radius.into());\n        self\n    }\n\n    /// Set rounded full\n    pub fn rounded_full(mut self) -> Self {\n        self.rounded = Some(rems(1.).into());\n        self\n    }\n}\n\nimpl Sizable for Tag {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl ParentElement for Tag {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl Styled for Tag {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Tag {\n    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let bg = if self.outline {\n            transparent_white()\n        } else {\n            self.variant.bg(cx)\n        };\n        let fg = self.variant.fg(self.outline, cx);\n        let border = self.variant.border(cx);\n        let rounded = self.rounded.unwrap_or(\n            match self.size {\n                Size::XSmall | Size::Small => cx.theme().radius / 2.,\n                _ => cx.theme().radius,\n            }\n            .into(),\n        );\n\n        div()\n            .flex()\n            .items_center()\n            .border_1()\n            .line_height(relative(1.))\n            .text_xs()\n            .map(|this| match self.size {\n                Size::XSmall | Size::Small => this.px_1p5().py_0p5(),\n                _ => this.px_2p5().py_1(),\n            })\n            .bg(bg)\n            .text_color(fg)\n            .border_color(border)\n            .rounded(rounded)\n            .hover(|this| this.opacity(0.9))\n            .refine_style(&self.style)\n            .children(self.children)\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/text/document.rs",
    "content": "use gpui::{\n    App, InteractiveElement as _, IntoElement, ListState, ParentElement as _, SharedString,\n    Styled as _, Window, div,\n};\n\nuse crate::text::node::{BlockNode, NodeContext};\n\n/// The parsed document AST.\n#[derive(Debug, Clone, PartialEq, Default)]\npub(crate) struct ParsedDocument {\n    pub(crate) source: SharedString,\n    pub(crate) blocks: Vec<BlockNode>,\n}\n\n#[derive(Default, Clone, Copy)]\npub(crate) struct NodeRenderOptions {\n    pub(crate) ix: usize,\n    pub(crate) in_list: bool,\n    pub(crate) todo: bool,\n    pub(crate) ordered: bool,\n    pub(crate) depth: usize,\n    pub(crate) is_last: bool,\n}\n\nimpl NodeRenderOptions {\n    pub(crate) fn is_last(mut self, is_last: bool) -> Self {\n        self.is_last = is_last;\n        self\n    }\n}\n\nimpl ParsedDocument {\n    pub(super) fn selected_text(&self) -> String {\n        let mut text = String::new();\n        for block in self.blocks.iter() {\n            text.push_str(&block.selected_text());\n        }\n        text\n    }\n\n    /// Converts the node to markdown format.\n    ///\n    /// This is used to generate markdown for test.\n    #[allow(dead_code)]\n    pub(crate) fn to_markdown(&self) -> String {\n        self.blocks\n            .iter()\n            .map(|child| child.to_markdown())\n            .collect::<Vec<_>>()\n            .join(\"\\n\\n\")\n    }\n\n    pub(super) fn render_root(\n        &self,\n        list_state: Option<ListState>,\n        node_cx: &NodeContext,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> impl IntoElement {\n        let Some(list_state) = list_state else {\n            let blocks_len = self.blocks.len();\n            return div()\n                .id(\"document\")\n                .children(self.blocks.iter().enumerate().map(move |(ix, node)| {\n                    let is_last = ix + 1 == blocks_len;\n                    node.render_block(\n                        NodeRenderOptions {\n                            ix,\n                            is_last,\n                            ..Default::default()\n                        },\n                        node_cx,\n                        window,\n                        cx,\n                    )\n                }));\n        };\n\n        let options = NodeRenderOptions {\n            is_last: true,\n            ..Default::default()\n        };\n\n        let blocks = &self.blocks;\n\n        if list_state.item_count() != blocks.len() {\n            list_state.reset(blocks.len());\n        }\n\n        div().id(\"document\").size_full().child(\n            gpui::list(list_state, {\n                let node_cx = node_cx.clone();\n                let blocks = blocks.clone();\n                move |ix, window, cx| {\n                    let is_last = ix + 1 == blocks.len();\n                    blocks[ix]\n                        .render_block(\n                            NodeRenderOptions {\n                                ix,\n                                is_last,\n                                ..options\n                            },\n                            &node_cx,\n                            window,\n                            cx,\n                        )\n                        .into_any_element()\n                }\n            })\n            .size_full(),\n        )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/text/format/html.rs",
    "content": "use std::cell::RefCell;\nuse std::collections::HashMap;\nuse std::ops::Range;\nuse std::rc::Rc;\n\nuse gpui::{DefiniteLength, SharedString, px, relative};\nuse html5ever::tendril::TendrilSink;\nuse html5ever::{LocalName, ParseOpts, local_name, parse_document};\nuse markup5ever_rcdom::{Node, NodeData, RcDom};\n\nuse crate::text::document::ParsedDocument;\nuse crate::text::node::{\n    self, BlockNode, ImageNode, InlineNode, LinkMark, NodeContext, Paragraph, Table, TableRow,\n    TextMark,\n};\n\nconst BLOCK_ELEMENTS: [&str; 35] = [\n    \"html\",\n    \"body\",\n    \"head\",\n    \"address\",\n    \"article\",\n    \"aside\",\n    \"blockquote\",\n    \"details\",\n    \"summary\",\n    \"dialog\",\n    \"div\",\n    \"dl\",\n    \"fieldset\",\n    \"figcaption\",\n    \"figure\",\n    \"footer\",\n    \"form\",\n    \"h1\",\n    \"h2\",\n    \"h3\",\n    \"h4\",\n    \"h5\",\n    \"h6\",\n    \"header\",\n    \"hr\",\n    \"main\",\n    \"nav\",\n    \"ol\",\n    \"p\",\n    \"pre\",\n    \"section\",\n    \"table\",\n    \"ul\",\n    \"style\",\n    \"script\",\n];\n\n/// Parse HTML into AST Node.\npub(crate) fn parse(source: &str, cx: &mut NodeContext) -> Result<ParsedDocument, SharedString> {\n    let opts = ParseOpts {\n        ..Default::default()\n    };\n\n    let bytes = cleanup_html(&source);\n    let mut cursor = std::io::Cursor::new(bytes);\n    // Ref\n    // https://github.com/servo/html5ever/blob/main/rcdom/examples/print-rcdom.rs\n    let dom = parse_document(RcDom::default(), opts)\n        .from_utf8()\n        .read_from(&mut cursor)\n        .map_err(|e| SharedString::from(format!(\"{:?}\", e)))?;\n\n    let mut paragraph = Paragraph::default();\n    // NOTE: The outer paragraph is not used.\n    let node: BlockNode =\n        parse_node(&dom.document, &mut paragraph, cx).unwrap_or(BlockNode::Unknown);\n    let node = node.compact();\n\n    Ok(ParsedDocument {\n        source: source.to_string().into(),\n        blocks: vec![node],\n    })\n}\n\nfn cleanup_html(source: &str) -> Vec<u8> {\n    let mut w = std::io::Cursor::new(vec![]);\n    let mut r = std::io::Cursor::new(source);\n    let mut minify = super::html5minify::Minifier::new(&mut w);\n    minify.omit_doctype(true);\n    if let Ok(()) = minify.minify(&mut r) {\n        w.into_inner()\n    } else {\n        source.bytes().collect()\n    }\n}\n\nfn attr_value(attrs: &RefCell<Vec<html5ever::Attribute>>, name: LocalName) -> Option<String> {\n    attrs.borrow().iter().find_map(|attr| {\n        if attr.name.local == name {\n            Some(attr.value.to_string())\n        } else {\n            None\n        }\n    })\n}\n\n/// Get style properties to HashMap\n/// TODO: Use cssparser to parse style attribute.\nfn style_attrs(attrs: &RefCell<Vec<html5ever::Attribute>>) -> HashMap<String, String> {\n    let mut styles = HashMap::new();\n    let Some(css_text) = attr_value(attrs, local_name!(\"style\")) else {\n        return styles;\n    };\n\n    for decl in css_text.split(';') {\n        let mut parts = decl.splitn(2, ':');\n        if let (Some(key), Some(value)) = (parts.next(), parts.next()) {\n            styles.insert(\n                key.trim().to_lowercase().to_string(),\n                value.trim().to_string(),\n            );\n        }\n    }\n\n    styles\n}\n\n/// Parse length value from style attribute.\n///\n/// When is percentage, it will be converted to relative length.\n/// Else, it will be converted to pixels.\nfn value_to_length(value: &str) -> Option<DefiniteLength> {\n    if value.ends_with(\"%\") {\n        value\n            .trim_end_matches(\"%\")\n            .parse::<f32>()\n            .ok()\n            .map(|v| relative(v / 100.))\n    } else {\n        value\n            .trim_end_matches(\"px\")\n            .parse()\n            .ok()\n            .map(|v| px(v).into())\n    }\n}\n\n/// Get width, height from attributes or parse them from style attribute.\nfn attr_width_height(\n    attrs: &RefCell<Vec<html5ever::Attribute>>,\n) -> (Option<DefiniteLength>, Option<DefiniteLength>) {\n    let mut width = None;\n    let mut height = None;\n\n    if let Some(value) = attr_value(attrs, local_name!(\"width\")) {\n        width = value_to_length(&value);\n    }\n\n    if let Some(value) = attr_value(attrs, local_name!(\"height\")) {\n        height = value_to_length(&value);\n    }\n\n    if width.is_none() || height.is_none() {\n        let styles = style_attrs(attrs);\n        if width.is_none() {\n            width = styles.get(\"width\").and_then(|v| value_to_length(&v));\n        }\n        if height.is_none() {\n            height = styles.get(\"height\").and_then(|v| value_to_length(&v));\n        }\n    }\n\n    (width, height)\n}\n\nfn parse_table_row(table: &mut Table, node: &Rc<Node>) {\n    let mut row = TableRow::default();\n    let mut count = 0;\n    for child in node.children.borrow().iter() {\n        match child.data {\n            NodeData::Element {\n                ref name,\n                ref attrs,\n                ..\n            } if name.local == local_name!(\"td\") || name.local == local_name!(\"th\") => {\n                if child.children.borrow().is_empty() {\n                    continue;\n                }\n\n                count += 1;\n                parse_table_cell(&mut row, child, attrs);\n            }\n            _ => {}\n        }\n    }\n\n    if count > 0 {\n        table.children.push(row);\n    }\n}\n\nfn parse_table_cell(\n    row: &mut node::TableRow,\n    node: &Rc<Node>,\n    attrs: &RefCell<Vec<html5ever::Attribute>>,\n) {\n    let mut paragraph = Paragraph::default();\n    for child in node.children.borrow().iter() {\n        parse_paragraph(&mut paragraph, child);\n    }\n    let width = attr_width_height(attrs).0;\n    let table_cell = node::TableCell {\n        children: paragraph,\n        width,\n    };\n    row.children.push(table_cell);\n}\n\n/// Trim text but leave at least one space.\n///\n/// - Before: \" \\r\\n Hello world \\t \"\n/// - After: \" Hello world \"\n#[allow(dead_code)]\nfn trim_text(text: &str) -> String {\n    let mut out = String::with_capacity(text.len());\n\n    for (i, c) in text.chars().enumerate() {\n        if c.is_whitespace() {\n            if i > 0 && out.ends_with(' ') {\n                continue;\n            }\n        }\n        out.push(c);\n    }\n\n    out\n}\n\nfn parse_paragraph(\n    paragraph: &mut Paragraph,\n    node: &Rc<Node>,\n) {\n    fn push_merged(paragraph: &mut Paragraph, text: String, marks: Vec<(Range<usize>, TextMark)>, new_mark: Option<TextMark>) {\n        if text.is_empty() {\n            return;\n        }\n        let mut node = InlineNode::new(text).marks(marks);\n        if let Some(new_mark) = new_mark {\n            let len = node.text.len();\n            if let Some(last) = node.marks.last_mut() && last.0.start == 0 && last.0.end == len {\n                last.1.merge(new_mark);\n            } else {\n                node.marks.push((0..node.text.len(), new_mark));\n            }\n        }\n        paragraph.push(node);\n    }\n\n    fn merge_children_with_mark(node: &Node, paragraph: &mut Paragraph, new_mark: Option<TextMark>) {\n        let mut merged_text = String::new();\n        let mut merged_marks = Vec::new();\n\n        for child in node.children.borrow().iter() {\n            let mut child_paragraph = Paragraph::default();\n            parse_paragraph(&mut child_paragraph, &child);\n\n            for node in child_paragraph.children {\n                let offset = merged_text.len();\n                merged_text.push_str(&node.text);\n                for (range, child_mark) in node.marks {\n                    merged_marks.push((range.start+offset .. range.end+offset, child_mark));\n                }\n\n                if let Some(mut image) = node.image {\n                    if let Some(link_mark) = new_mark.as_ref().and_then(|mark| mark.link.clone()) {\n                        image.link = Some(link_mark);\n                    }\n\n                    push_merged(paragraph, std::mem::take(&mut merged_text),\n                        std::mem::take(&mut merged_marks), new_mark.clone());\n\n                    paragraph.push(InlineNode::image(image));\n                }\n            }\n        }\n\n        push_merged(paragraph, merged_text, merged_marks, new_mark.clone());\n    }\n\n    match &node.data {\n        NodeData::Text { contents } => {\n            let part = &contents.borrow();\n            paragraph.push_str(&part);\n        }\n        NodeData::Element { name, attrs, .. } => match name.local {\n            local_name!(\"em\") | local_name!(\"i\") => {\n                merge_children_with_mark(node, paragraph, Some(TextMark::default().italic()));\n            }\n            local_name!(\"strong\") | local_name!(\"b\") => {\n                merge_children_with_mark(node, paragraph, Some(TextMark::default().bold()));\n            }\n            local_name!(\"del\") | local_name!(\"s\") => {\n                merge_children_with_mark(node, paragraph, Some(TextMark::default().strikethrough()));\n            }\n            local_name!(\"code\") => {\n                merge_children_with_mark(node, paragraph, Some(TextMark::default().code()));\n            }\n            local_name!(\"a\") => {\n                let link_mark = LinkMark {\n                    url: attr_value(&attrs, local_name!(\"href\"))\n                        .unwrap_or_default()\n                        .into(),\n                    title: attr_value(&attrs, local_name!(\"title\")).map(Into::into),\n                    ..Default::default()\n                };\n\n                merge_children_with_mark(node, paragraph, Some(TextMark::default().link(link_mark)));\n            }\n            local_name!(\"img\") => {\n                let Some(src) = attr_value(attrs, local_name!(\"src\")) else {\n                    if cfg!(debug_assertions) {\n                        tracing::warn!(\"Image node missing src attribute\");\n                    }\n                    return;\n                };\n\n                let alt = attr_value(attrs, local_name!(\"alt\"));\n                let title = attr_value(attrs, local_name!(\"title\"));\n                let (width, height) = attr_width_height(attrs);\n\n                paragraph.push_image(ImageNode {\n                    url: src.into(),\n                    link: None,\n                    alt: alt.map(Into::into),\n                    width,\n                    height,\n                    title: title.map(Into::into),\n                });\n            }\n            _ => {\n                merge_children_with_mark(node, paragraph, None);\n            }\n        },\n        _ => {\n            merge_children_with_mark(node, paragraph, None);\n        }\n    }\n}\n\nfn parse_node(\n    node: &Rc<Node>,\n    paragraph: &mut Paragraph,\n    cx: &mut NodeContext,\n) -> Option<BlockNode> {\n    match node.data {\n        NodeData::Text { ref contents } => {\n            let text = contents.borrow().to_string();\n            if text.len() > 0 {\n                paragraph.push_str(&text);\n            }\n\n            None\n        }\n        NodeData::Element {\n            ref name,\n            ref attrs,\n            ..\n        } => match name.local {\n            local_name!(\"br\") => Some(BlockNode::Break {\n                html: true,\n                span: None,\n            }),\n            local_name!(\"h1\")\n            | local_name!(\"h2\")\n            | local_name!(\"h3\")\n            | local_name!(\"h4\")\n            | local_name!(\"h5\")\n            | local_name!(\"h6\") => {\n                let mut children = vec![];\n                consume_paragraph(&mut children, paragraph);\n\n                let level = name\n                    .local\n                    .chars()\n                    .last()\n                    .unwrap_or('6')\n                    .to_digit(10)\n                    .unwrap_or(6) as u8;\n\n                let mut paragraph = Paragraph::default();\n                for child in node.children.borrow().iter() {\n                    parse_paragraph(&mut paragraph, child);\n                }\n\n                let heading = BlockNode::Heading {\n                    level,\n                    children: paragraph,\n                    span: None,\n                };\n                if children.len() > 0 {\n                    children.push(heading);\n\n                    Some(BlockNode::Root {\n                        children,\n                        span: None,\n                    })\n                } else {\n                    Some(heading)\n                }\n            }\n            local_name!(\"img\") => {\n                let mut children = vec![];\n                consume_paragraph(&mut children, paragraph);\n\n                let Some(src) = attr_value(attrs, local_name!(\"src\")) else {\n                    if cfg!(debug_assertions) {\n                        tracing::warn!(\"image node missing src attribute\");\n                    }\n                    return None;\n                };\n\n                let alt = attr_value(&attrs, local_name!(\"alt\"));\n                let title = attr_value(&attrs, local_name!(\"title\"));\n                let (width, height) = attr_width_height(&attrs);\n\n                let mut paragraph = Paragraph::default();\n                paragraph.push_image(ImageNode {\n                    url: src.into(),\n                    link: None,\n                    title: title.map(Into::into),\n                    alt: alt.map(Into::into),\n                    width,\n                    height,\n                });\n\n                if children.len() > 0 {\n                    children.push(BlockNode::Paragraph(paragraph));\n                    Some(BlockNode::Root {\n                        children,\n                        span: None,\n                    })\n                } else {\n                    Some(BlockNode::Paragraph(paragraph))\n                }\n            }\n            local_name!(\"ul\") | local_name!(\"ol\") => {\n                let ordered = name.local == local_name!(\"ol\");\n                let children = consume_children_nodes(node, paragraph, cx);\n                Some(BlockNode::List {\n                    children,\n                    ordered,\n                    span: None,\n                })\n            }\n            local_name!(\"li\") => {\n                let mut children = vec![];\n                consume_paragraph(&mut children, paragraph);\n\n                for child in node.children.borrow().iter() {\n                    let mut child_paragraph = Paragraph::default();\n                    if let Some(child_node) = parse_node(child, &mut child_paragraph, cx) {\n                        children.push(child_node);\n                    }\n                    if child_paragraph.text_len() > 0 {\n                        // If last child is paragraph, merge child\n                        if let Some(last_child) = children.last_mut() {\n                            if let BlockNode::Paragraph(last_paragraph) = last_child {\n                                last_paragraph.merge(child_paragraph);\n                                continue;\n                            }\n                        }\n\n                        children.push(BlockNode::Paragraph(child_paragraph));\n                    }\n                }\n\n                consume_paragraph(&mut children, paragraph);\n\n                Some(BlockNode::ListItem {\n                    children,\n                    spread: false,\n                    checked: None,\n                    span: None,\n                })\n            }\n            local_name!(\"table\") => {\n                let mut children = vec![];\n                consume_paragraph(&mut children, paragraph);\n\n                let mut table = Table::default();\n                for child in node.children.borrow().iter() {\n                    match child.data {\n                        NodeData::Element { ref name, .. }\n                            if name.local == local_name!(\"tbody\")\n                                || name.local == local_name!(\"thead\") =>\n                        {\n                            for sub_child in child.children.borrow().iter() {\n                                parse_table_row(&mut table, &sub_child);\n                            }\n                        }\n                        _ => {\n                            parse_table_row(&mut table, &child);\n                        }\n                    }\n                }\n                consume_paragraph(&mut children, paragraph);\n\n                let table = BlockNode::Table(table);\n                if children.len() > 0 {\n                    children.push(table);\n                    Some(BlockNode::Root {\n                        children,\n                        span: None,\n                    })\n                } else {\n                    Some(table)\n                }\n            }\n            local_name!(\"blockquote\") => {\n                let children = consume_children_nodes(node, paragraph, cx);\n                Some(BlockNode::Blockquote {\n                    children,\n                    span: None,\n                })\n            }\n            local_name!(\"style\") | local_name!(\"script\") => None,\n            _ => {\n                if BLOCK_ELEMENTS.contains(&name.local.trim()) {\n                    let mut children: Vec<BlockNode> = vec![];\n\n                    // Case:\n                    //\n                    // Hello <p>Inner text of block element</p> World\n\n                    // Insert before text as a node -- The \"Hello\"\n                    consume_paragraph(&mut children, paragraph);\n\n                    // Inner of the block element -- The \"Inner text of block element\"\n                    for child in node.children.borrow().iter() {\n                        if let Some(child_node) = parse_node(child, paragraph, cx) {\n                            children.push(child_node);\n                        }\n                    }\n                    consume_paragraph(&mut children, paragraph);\n\n                    if children.is_empty() {\n                        None\n                    } else {\n                        Some(BlockNode::Root {\n                            children,\n                            span: None,\n                        })\n                    }\n                } else {\n                    // Others to as Inline\n                    parse_paragraph(paragraph, node);\n\n                    if paragraph.is_image() {\n                        Some(BlockNode::Paragraph(paragraph.take()))\n                    } else {\n                        None\n                    }\n                }\n            }\n        },\n        NodeData::Document => {\n            let children = consume_children_nodes(node, paragraph, cx);\n            Some(BlockNode::Root {\n                children,\n                span: None,\n            })\n        }\n        NodeData::Doctype { .. }\n        | NodeData::Comment { .. }\n        | NodeData::ProcessingInstruction { .. } => None,\n    }\n}\n\nfn consume_children_nodes(\n    node: &Node,\n    paragraph: &mut Paragraph,\n    cx: &mut NodeContext,\n) -> Vec<BlockNode> {\n    let mut children = vec![];\n    consume_paragraph(&mut children, paragraph);\n    for child in node.children.borrow().iter() {\n        if let Some(child_node) = parse_node(child, paragraph, cx) {\n            children.push(child_node);\n        }\n        consume_paragraph(&mut children, paragraph);\n    }\n\n    children\n}\n\nfn consume_paragraph(children: &mut Vec<BlockNode>, paragraph: &mut Paragraph) {\n    if paragraph.is_empty() {\n        return;\n    }\n\n    children.push(BlockNode::Paragraph(paragraph.take()));\n}\n\n#[cfg(test)]\nmod tests {\n    use gpui::{px, relative};\n\n    use crate::text::{\n        document::ParsedDocument,\n        node::{BlockNode, ImageNode, InlineNode, NodeContext, Paragraph},\n    };\n\n    use super::trim_text;\n\n    #[test]\n    fn test_cleanup_html() {\n        let html = r#\"<p>\n            and\n            <code>code</code>\n            text\n        </p>\"#;\n        let cleaned = super::cleanup_html(html);\n        assert_eq!(\n            String::from_utf8(cleaned).unwrap(),\n            \"<p>and <code>code</code> text\"\n        );\n\n        let html = r#\"<p>\n            and\n            <em>   <code>code</code>   <i>italic</i>   </em>\n            text\n        </p>\"#;\n        let cleaned = super::cleanup_html(html);\n        assert_eq!(\n            String::from_utf8(cleaned).unwrap(),\n            \"<p>and <em><code>code</code> <i>italic</i></em> text\"\n        );\n    }\n\n    #[test]\n    fn test_trim_text() {\n        assert_eq!(trim_text(\"  \\n\\tHello world \\t\\r \"), \" Hello world \",);\n    }\n\n    #[test]\n    fn test_keep_spaces() {\n        let html = r#\"<p>and <code>code</code> text</p>\"#;\n        let mut cx = NodeContext::default();\n        let node = super::parse(html, &mut cx).unwrap();\n        assert_eq!(node.to_markdown(), \"and `code` text\");\n\n        let html = r#\"\n            <div>\n            <p>\n                and\n                <em>   <code>code</code>   <i>italic</i>   </em>\n                text\n            </p>\n            <p>\n                <img src=\"https://example.com/image.png\" alt=\"Example\" width=\"100\" height=\"200\" title=\"Example Image\" />\n            </p>\n            <ul>\n                <li>Item 1</li>\n                <li>Item 2\n                </li>\n            </ul>\n            </div>\n        \"#;\n        let node = super::parse(html, &mut cx).unwrap();\n        assert_eq!(\n            node.to_markdown(),\n            indoc::indoc! {r#\"\n            and *code italic* text\n\n            ![Example](https://example.com/image.png \"Example Image\")\n\n            - Item 1\n            - Item 2\n            \"#}\n            .trim()\n        );\n    }\n\n    #[test]\n    fn test_value_to_length() {\n        assert_eq!(super::value_to_length(\"100px\"), Some(px(100.).into()));\n        assert_eq!(super::value_to_length(\"100%\"), Some(relative(1.)));\n        assert_eq!(super::value_to_length(\"56%\"), Some(relative(0.56)));\n        assert_eq!(super::value_to_length(\"240\"), Some(px(240.).into()));\n    }\n\n    #[test]\n    fn test_image() {\n        let html = r#\"<img src=\"https://example.com/image.png\" alt=\"Example\" width=\"100\" height=\"200\" title=\"Example Image\" />\"#;\n        let mut cx = NodeContext::default();\n        let node = super::parse(html, &mut cx).unwrap();\n        assert_eq!(\n            node,\n            ParsedDocument {\n                source: html.to_string().into(),\n                blocks: vec![BlockNode::Paragraph(Paragraph {\n                    span: None,\n                    children: vec![InlineNode::image(ImageNode {\n                        url: \"https://example.com/image.png\".to_string().into(),\n                        alt: Some(\"Example\".to_string().into()),\n                        width: Some(px(100.).into()),\n                        height: Some(px(200.).into()),\n                        title: Some(\"Example Image\".to_string().into()),\n                        ..Default::default()\n                    })],\n                    ..Default::default()\n                })]\n            }\n        );\n\n        let html = r#\"<img src=\"https://example.com/image.png\" alt=\"Example\" style=\"width: 80%\" title=\"Example Image\" />\"#;\n        let node = super::parse(html, &mut cx).unwrap();\n        assert_eq!(\n            node,\n            ParsedDocument {\n                source: html.to_string().into(),\n                blocks: vec![BlockNode::Paragraph(Paragraph {\n                    span: None,\n                    children: vec![InlineNode::image(ImageNode {\n                        url: \"https://example.com/image.png\".to_string().into(),\n                        alt: Some(\"Example\".to_string().into()),\n                        width: Some(relative(0.8)),\n                        height: None,\n                        title: Some(\"Example Image\".to_string().into()),\n                        ..Default::default()\n                    })],\n                    ..Default::default()\n                })]\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/text/format/html5minify/mod.rs",
    "content": "//! HTML5 markup minifier.\n//!\n//! This is a fork of the `html5minify` crate.\n//! https://github.com/martingallagher/html5minify\n\nuse std::{cell::RefCell, io, rc::Rc, str};\n\nuse html5ever::{\n    Attribute, ParseOpts, QualName, parse_document,\n    tendril::{Tendril, TendrilSink, fmt::UTF8},\n};\nuse markup5ever_rcdom::{Node, NodeData, RcDom};\n\n/// Defines the minify trait.\n#[allow(dead_code)]\npub(crate) trait Minify {\n    /// Minifies the source returning the minified HTML5.\n    ///\n    /// # Errors\n    ///\n    /// Will return `Err` if unable to read from the input reader or unable to\n    /// write to the output writer.\n    fn minify(&self) -> Result<Vec<u8>, io::Error>;\n}\n\n/// Minifies the HTML input to the destination writer.\n/// Outputs HTML5; non-HTML5 input will be transformed to HTML5.\n///\n/// # Errors\n///\n/// Will return `Err` if unable to read from the input reader or unable to write\n/// to the output writer.\n#[inline]\n#[allow(dead_code)]\npub(crate) fn minify<R: io::Read, W: io::Write>(mut r: &mut R, w: &mut W) -> io::Result<()> {\n    Minifier::new(w).minify(&mut r)\n}\n\nimpl<T> Minify for T\nwhere\n    T: AsRef<[u8]>,\n{\n    #[inline]\n    fn minify(&self) -> Result<Vec<u8>, io::Error> {\n        let mut minified = vec![];\n\n        minify(&mut self.as_ref(), &mut minified)?;\n\n        Ok(minified)\n    }\n}\n\n/// Minifier implementation for `io::Write`.\n#[allow(clippy::struct_excessive_bools)]\npub struct Minifier<'a, W: io::Write> {\n    w: &'a mut W,\n    omit_doctype: bool,\n    collapse_whitespace: bool,\n    preserve_comments: bool,\n    preceding_whitespace: bool,\n}\n\n/// Holds node positional context.\nstruct Context<'a> {\n    parent: &'a Node,\n    parent_context: Option<&'a Context<'a>>,\n    left: Option<&'a [Rc<Node>]>,\n    right: Option<&'a [Rc<Node>]>,\n}\n\nimpl<'a> Context<'a> {\n    /// Determine whether to trim whitespace.\n    /// Uses naive HTML5 whitespace collapsing rules.\n    fn trim(&self, preceding_whitespace: bool) -> (bool, bool) {\n        (preceding_whitespace || self.trim_left(), self.trim_right())\n    }\n\n    fn trim_left(&self) -> bool {\n        self.left.map_or_else(\n            || is_block_element(self.parent) || self.parent_trim_left(),\n            |siblings| {\n                siblings\n                    .iter()\n                    .rev()\n                    .find_map(Self::is_block_element)\n                    .unwrap_or_else(|| self.parent_trim_left())\n            },\n        )\n    }\n\n    fn parent_trim_left(&self) -> bool {\n        self.parent_context.map_or(true, Context::trim_left)\n    }\n\n    fn trim_right(&self) -> bool {\n        self.right.map_or(true, |siblings| {\n            siblings\n                .iter()\n                .find_map(Self::is_block_element)\n                .unwrap_or(true)\n        })\n    }\n\n    fn next_element(&self) -> Option<&Rc<Node>> {\n        self.right.and_then(|siblings| {\n            siblings\n                .iter()\n                .find(|node| matches!(node.data, NodeData::Element { .. }))\n        })\n    }\n\n    fn is_block_element(node: &Rc<Node>) -> Option<bool> {\n        if let NodeData::Element { name, .. } = &node.data {\n            Some(is_block_element_name(name.local.as_ref()))\n        } else {\n            None\n        }\n    }\n}\n\nimpl<'a, W> Minifier<'a, W>\nwhere\n    W: io::Write,\n{\n    /// Creates a new `Minifier` instance.\n    #[inline]\n    pub fn new(w: &'a mut W) -> Self {\n        Self {\n            w,\n            omit_doctype: false,\n            collapse_whitespace: true,\n            preserve_comments: false,\n            preceding_whitespace: false,\n        }\n    }\n\n    /// Collapse whitespace between elements and in text when whitespace isn't preserved by default.\n    /// Enabled by default.\n    #[inline]\n    #[allow(dead_code)]\n    pub fn collapse_whitespace(&mut self, collapse: bool) -> &mut Self {\n        self.collapse_whitespace = collapse;\n        self\n    }\n\n    /// Omit writing the HTML5 doctype.\n    /// Disabled by default.\n    #[inline]\n    #[allow(dead_code)]\n    pub fn omit_doctype(&mut self, omit: bool) -> &mut Self {\n        self.omit_doctype = omit;\n        self\n    }\n\n    /// Preserve HTML comments.\n    /// Disabled by default.\n    #[inline]\n    #[allow(dead_code)]\n    pub fn preserve_comments(&mut self, preserve: bool) -> &mut Self {\n        self.preserve_comments = preserve;\n        self\n    }\n\n    /// Minifies the given reader input.\n    ///\n    /// # Errors\n    ///\n    /// Will return `Err` if unable to write to the output writer.\n    #[inline]\n    #[allow(dead_code)]\n    pub fn minify<R: io::Read>(&mut self, mut r: &mut R) -> io::Result<()> {\n        let dom = parse_document(RcDom::default(), ParseOpts::default())\n            .from_utf8()\n            .read_from(&mut r)?;\n\n        if !self.omit_doctype {\n            self.w.write_all(b\"<!doctype html>\")?;\n        }\n\n        self.minify_node(&None, &dom.document)\n    }\n\n    fn minify_node<'b>(&mut self, ctx: &'b Option<Context>, node: &'b Node) -> io::Result<()> {\n        match &node.data {\n            NodeData::Text { contents } => {\n                // Check if whitespace collapsing disabled\n                let contents = contents.borrow();\n                let contents = contents.as_ref();\n\n                if !self.collapse_whitespace {\n                    return self.w.write_all(contents.as_bytes());\n                }\n\n                // Check if parent is whitespace preserving element or contains code (<script>, <style>)\n                let (skip_collapse_whitespace, contains_code) =\n                    ctx.as_ref().map_or((false, false), |ctx| {\n                        if let NodeData::Element { name, .. } = &ctx.parent.data {\n                            let name = name.local.as_ref();\n\n                            (preserve_whitespace(name), contains_code(name))\n                        } else {\n                            (false, false)\n                        }\n                    });\n\n                if skip_collapse_whitespace {\n                    return self.w.write_all(contents.as_bytes());\n                }\n\n                if contains_code {\n                    return self\n                        .w\n                        .write_all(contents.trim_matches(is_ascii_whitespace).as_bytes());\n                }\n\n                // Early exit if empty to forego expensive trim logic\n                if contents.is_empty() {\n                    return io::Result::Ok(());\n                }\n\n                let (trim_left, trim_right) = ctx\n                    .as_ref()\n                    .map_or((true, true), |ctx| ctx.trim(self.preceding_whitespace));\n                let contents = match (trim_left, trim_right) {\n                    (true, true) => contents.trim_matches(is_ascii_whitespace),\n                    (true, false) => contents.trim_start_matches(is_ascii_whitespace),\n                    (false, true) => contents.trim_end_matches(is_ascii_whitespace),\n                    _ => contents,\n                };\n\n                // Second empty check after trimming whitespace\n                if !contents.is_empty() {\n                    // replace \\n, \\r to ' '\n                    let contents = contents\n                        .bytes()\n                        .map(|c| if matches!(c, b'\\n' | b'\\r') { b' ' } else { c })\n                        .collect::<Vec<u8>>();\n\n                    self.write_collapse_whitespace(&contents, reserved_entity, None)?;\n\n                    self.preceding_whitespace = !trim_right\n                        && contents\n                            .iter()\n                            .last()\n                            .map_or(false, u8::is_ascii_whitespace);\n                }\n\n                Ok(())\n            }\n\n            NodeData::Comment { contents } if self.preserve_comments => {\n                self.w.write_all(b\"<!--\")?;\n                self.w.write_all(contents.as_bytes())?;\n                self.w.write_all(b\"-->\")\n            }\n\n            NodeData::Document => self.minify_children(ctx, node),\n\n            NodeData::Element { name, attrs, .. } => {\n                let attrs = attrs.borrow();\n                let tag = name.local.as_ref();\n\n                if is_self_closing(tag) {\n                    return self.write_start_tag(name, &attrs);\n                }\n\n                let (omit_start_tag, omit_end_tag) =\n                    self.omit_tags(ctx, node, tag, attrs.is_empty());\n\n                if !omit_start_tag {\n                    self.write_start_tag(name, &attrs)?;\n                }\n\n                self.minify_children(ctx, node)?;\n\n                if !omit_end_tag {\n                    self.write_end_tag(name)?;\n                }\n\n                Ok(())\n            }\n\n            _ => Ok(()),\n        }\n    }\n\n    fn next_is_comment<'b, I>(&self, v: I) -> bool\n    where\n        I: IntoIterator<Item = &'b Rc<Node>>,\n    {\n        v.into_iter()\n            .find_map(|node| match &node.data {\n                NodeData::Text { contents } => {\n                    if self.collapse_whitespace && is_whitespace(contents) {\n                        // Blocks of whitespace are skipped\n                        None\n                    } else {\n                        Some(false)\n                    }\n                }\n                NodeData::Comment { .. } => Some(self.preserve_comments),\n                _ => Some(false),\n            })\n            .unwrap_or(false)\n    }\n\n    fn is_whitespace(&self, s: &RefCell<Tendril<UTF8>>) -> Option<bool> {\n        if self.collapse_whitespace && is_whitespace(s) {\n            None\n        } else {\n            Some(\n                !s.borrow()\n                    .as_bytes()\n                    .iter()\n                    .next()\n                    .map_or(false, u8::is_ascii_whitespace),\n            )\n        }\n    }\n\n    /// Determines if start and end tags can be omitted.\n    /// Whitespace rules are ignored if `collapse_whitespace` is enabled.\n    #[allow(clippy::too_many_lines)]\n    fn omit_tags(\n        &self,\n        ctx: &Option<Context>,\n        node: &Node,\n        name: &str,\n        empty_attributes: bool,\n    ) -> (bool, bool) {\n        ctx.as_ref().map_or((false, false), |ctx| match name {\n            \"html\" => {\n                // The end tag may be omitted if the <html> element is not immediately followed by a comment.\n                let omit_end = ctx.right.map_or(true, |right| !self.next_is_comment(right));\n                // The start tag may be omitted if the first thing inside the <html> element is not a comment.\n                let omit_start =\n                    empty_attributes && omit_end && !self.next_is_comment(&*node.children.borrow());\n\n                (omit_start, omit_end)\n            }\n            \"head\" => {\n                // The end tag may be omitted if the first thing following the <head> element is not a space character or a comment.\n                let omit_end = ctx.right.map_or(true, |right| {\n                    right\n                        .iter()\n                        .find_map(|node| match &node.data {\n                            NodeData::Text { contents } => self.is_whitespace(contents),\n                            NodeData::Comment { .. } => {\n                                if self.preserve_comments {\n                                    Some(false)\n                                } else {\n                                    None\n                                }\n                            }\n                            _ => Some(true),\n                        })\n                        .unwrap_or(true)\n                });\n                // The start tag may be omitted if the first thing inside the <head> element is an element.\n                let omit_start = empty_attributes\n                    && omit_end\n                    && node\n                        .children\n                        .borrow()\n                        .iter()\n                        .find_map(|node| match &node.data {\n                            NodeData::Text { contents } => self.is_whitespace(contents),\n                            NodeData::Element { .. } => Some(true),\n                            NodeData::Comment { .. } => {\n                                if self.preserve_comments {\n                                    Some(false)\n                                } else {\n                                    None\n                                }\n                            }\n                            _ => Some(false),\n                        })\n                        .unwrap_or(true);\n\n                (omit_start, omit_end)\n            }\n            \"body\" => {\n                // The start tag may be omitted if the first thing inside it is not a space character, comment, <script> element or <style> element.\n                let omit_start = empty_attributes\n                    && node\n                        .children\n                        .borrow()\n                        .iter()\n                        .find_map(|node| match &node.data {\n                            NodeData::Text { contents } => self.is_whitespace(contents),\n                            NodeData::Element { name, .. } => {\n                                Some(!matches!(name.local.as_ref(), \"script\" | \"style\"))\n                            }\n                            NodeData::Comment { .. } => {\n                                if self.preserve_comments {\n                                    Some(false)\n                                } else {\n                                    None\n                                }\n                            }\n                            _ => Some(true),\n                        })\n                        .unwrap_or(true);\n                // The end tag may be omitted if the <body> element has contents or has a start tag, and is not immediately followed by a comment.\n                let omit_end = ctx.right.map_or(true, |right| !self.next_is_comment(right));\n\n                (omit_start && omit_end, omit_end)\n            }\n            \"p\" => {\n                let omit_end = ctx.next_element().map_or(true, |node| {\n                    if let NodeData::Element { name, .. } = &node.data {\n                        matches!(\n                            name.local.as_ref().to_ascii_lowercase().as_str(),\n                            \"address\"\n                                | \"article\"\n                                | \"aside\"\n                                | \"blockquote\"\n                                | \"div\"\n                                | \"dl\"\n                                | \"fieldset\"\n                                | \"footer\"\n                                | \"form\"\n                                | \"h1\"\n                                | \"h2\"\n                                | \"h3\"\n                                | \"h4\"\n                                | \"h5\"\n                                | \"h6\"\n                                | \"header\"\n                                | \"hr\"\n                                | \"menu\"\n                                | \"nav\"\n                                | \"ol\"\n                                | \"p\"\n                                | \"pre\"\n                                | \"section\"\n                                | \"table\"\n                                | \"ul\"\n                        )\n                    } else {\n                        false\n                    }\n                });\n\n                (false, omit_end)\n            }\n            // TODO: comprehensive handling of optional end element rules\n            _ => (false, optional_end_tag(name)),\n        })\n    }\n\n    #[allow(clippy::needless_pass_by_value)]\n    fn minify_children(&mut self, ctx: &Option<Context>, node: &Node) -> io::Result<()> {\n        let children = node.children.borrow();\n        let l = children.len();\n\n        children.iter().enumerate().try_for_each(|(i, child)| {\n            if self.preceding_whitespace && is_block_element(child) {\n                self.preceding_whitespace = false;\n            }\n\n            self.minify_node(\n                &Some(Context {\n                    parent: node,\n                    parent_context: ctx.as_ref(),\n                    left: if i > 0 { Some(&children[..i]) } else { None },\n                    right: if i + 1 < l {\n                        Some(&children[i + 1..])\n                    } else {\n                        None\n                    },\n                }),\n                child,\n            )\n        })\n    }\n\n    fn write_qualified_name(&mut self, name: &QualName) -> io::Result<()> {\n        if let Some(prefix) = &name.prefix {\n            self.w\n                .write_all(prefix.as_ref().to_ascii_lowercase().as_bytes())?;\n            self.w.write_all(b\":\")?;\n        }\n\n        self.w\n            .write_all(name.local.as_ref().to_ascii_lowercase().as_bytes())\n    }\n\n    fn write_start_tag(&mut self, name: &QualName, attrs: &[Attribute]) -> io::Result<()> {\n        self.w.write_all(b\"<\")?;\n        self.write_qualified_name(name)?;\n\n        attrs\n            .iter()\n            .try_for_each(|attr| self.write_attribute(attr))?;\n\n        self.w.write_all(b\">\")\n    }\n\n    fn write_end_tag(&mut self, name: &QualName) -> io::Result<()> {\n        self.w.write_all(b\"</\")?;\n        self.write_qualified_name(name)?;\n        self.w.write_all(b\">\")\n    }\n\n    fn write_attribute(&mut self, attr: &Attribute) -> io::Result<()> {\n        self.w.write_all(b\" \")?;\n        self.write_qualified_name(&attr.name)?;\n\n        let value = attr.value.as_ref();\n        let value = if self.collapse_whitespace {\n            value.trim_matches(is_ascii_whitespace)\n        } else {\n            value\n        };\n\n        if value.is_empty() {\n            return io::Result::Ok(());\n        }\n\n        self.w.write_all(b\"=\")?;\n\n        let b = value.as_bytes();\n        let (unquoted, double, _) =\n            b.iter()\n                .fold((true, false, false), |(unquoted, double, single), &c| {\n                    let (double, single) = (double || c == b'\"', single || c == b'\\'');\n                    let unquoted =\n                        unquoted && !double && !single && c != b'=' && !c.is_ascii_whitespace();\n\n                    (unquoted, double, single)\n                });\n\n        if unquoted {\n            self.w.write_all(b)\n        } else if double {\n            self.write_attribute_value(b, b\"'\", reserved_entity_with_apos)\n        } else {\n            self.write_attribute_value(b, b\"\\\"\", reserved_entity)\n        }\n    }\n\n    fn write_attribute_value<T: AsRef<[u8]>>(\n        &mut self,\n        v: T,\n        quote: &[u8],\n        f: EntityFn,\n    ) -> io::Result<()> {\n        self.w.write_all(quote)?;\n\n        let b = v.as_ref();\n\n        if self.collapse_whitespace {\n            self.write_collapse_whitespace(b, f, Some(false))\n        } else {\n            self.w.write_all(b)\n        }?;\n\n        self.w.write_all(quote)\n    }\n\n    /// Efficiently writes blocks of content, e.g. a string with no collapsed\n    /// whitespace would result in a single write.\n    fn write_collapse_whitespace(\n        &mut self,\n        b: &[u8],\n        f: EntityFn,\n        preceding_whitespace: Option<bool>,\n    ) -> io::Result<()> {\n        b.iter()\n            .enumerate()\n            .try_fold(\n                (0, preceding_whitespace.unwrap_or(self.preceding_whitespace)),\n                |(pos, preceding_whitespace), (i, &c)| {\n                    let is_whitespace = c.is_ascii_whitespace();\n\n                    Ok(if is_whitespace && preceding_whitespace {\n                        if i != pos {\n                            self.write(&b[pos..i], f)?;\n                        }\n\n                        // ASCII whitespace = 1 byte\n                        (i + 1, true)\n                    } else {\n                        (pos, is_whitespace)\n                    })\n                },\n            )\n            .and_then(|(pos, _)| {\n                if pos < b.len() {\n                    self.write(&b[pos..], f)?;\n                }\n\n                Ok(())\n            })\n    }\n\n    fn write(&mut self, b: &[u8], f: EntityFn) -> io::Result<()> {\n        b.iter()\n            .enumerate()\n            .try_fold(0, |pos, (i, &c)| {\n                Ok(if let Some(entity) = f(c) {\n                    self.w.write_all(&b[pos..i])?;\n                    self.w.write_all(entity)?;\n\n                    // Reserved characters are 1 byte\n                    i + 1\n                } else {\n                    pos\n                })\n            })\n            .and_then(|pos| {\n                if pos < b.len() {\n                    self.w.write_all(&b[pos..])?;\n                }\n\n                Ok(())\n            })\n    }\n}\n\ntype EntityFn = fn(u8) -> Option<&'static [u8]>;\n\nconst fn reserved_entity(v: u8) -> Option<&'static [u8]> {\n    match v {\n        b'<' => Some(b\"&lt;\"),\n        b'>' => Some(b\"&gt;\"),\n        b'&' => Some(b\"&#38;\"),\n        _ => None,\n    }\n}\n\nconst fn reserved_entity_with_apos(v: u8) -> Option<&'static [u8]> {\n    if v == b'\\'' {\n        Some(b\"&#39;\")\n    } else {\n        reserved_entity(v)\n    }\n}\n\nfn is_whitespace(s: &RefCell<Tendril<UTF8>>) -> bool {\n    s.borrow().as_bytes().iter().all(u8::is_ascii_whitespace)\n}\n\nfn is_block_element_name(name: &str) -> bool {\n    matches!(\n        name,\n        \"address\"\n            | \"article\"\n            | \"aside\"\n            | \"blockquote\"\n            | \"body\"\n            | \"br\"\n            | \"details\"\n            | \"dialog\"\n            | \"dd\"\n            | \"div\"\n            | \"dl\"\n            | \"dt\"\n            | \"fieldset\"\n            | \"figcaption\"\n            | \"figure\"\n            | \"footer\"\n            | \"form\"\n            | \"h1\"\n            | \"h2\"\n            | \"h3\"\n            | \"h4\"\n            | \"h5\"\n            | \"h6\"\n            | \"head\"\n            | \"header\"\n            | \"hgroup\"\n            | \"hr\"\n            | \"html\"\n            | \"li\"\n            | \"link\"\n            | \"main\"\n            | \"meta\"\n            | \"nav\"\n            | \"ol\"\n            | \"option\"\n            | \"p\"\n            | \"pre\"\n            | \"script\"\n            | \"section\"\n            | \"source\"\n            | \"table\"\n            | \"td\"\n            | \"th\"\n            | \"title\"\n            | \"tr\"\n            | \"ul\"\n    )\n}\n\nfn is_block_element(node: &Node) -> bool {\n    match &node.data {\n        NodeData::Element { name, .. } => is_block_element_name(name.local.as_ref()),\n        NodeData::Document => true,\n        _ => false,\n    }\n}\n\n#[allow(clippy::missing_const_for_fn)]\nfn is_ascii_whitespace(c: char) -> bool {\n    c.is_ascii_whitespace()\n}\n\nfn preserve_whitespace(name: &str) -> bool {\n    matches!(name, \"pre\" | \"textarea\")\n}\n\nfn contains_code(name: &str) -> bool {\n    matches!(name, \"script\" | \"style\")\n}\n\nfn is_self_closing(name: &str) -> bool {\n    matches!(\n        name,\n        \"area\"\n            | \"base\"\n            | \"br\"\n            | \"col\"\n            | \"embed\"\n            | \"hr\"\n            | \"img\"\n            | \"input\"\n            | \"link\"\n            | \"meta\"\n            | \"param\"\n            | \"source\"\n            | \"track\"\n            | \"wbr\"\n            | \"command\"\n            | \"keygen\"\n            | \"menuitem\"\n    )\n}\n\nfn optional_end_tag(name: &str) -> bool {\n    matches!(\n        name,\n        \"basefont\"\n            | \"colgroup\"\n            | \"dd\"\n            | \"dt\"\n            | \"frame\"\n            | \"isindex\"\n            | \"li\"\n            | \"option\"\n            | \"p\"\n            | \"tbody\"\n            | \"td\"\n            | \"tfoot\"\n            | \"th\"\n            | \"thead\"\n            | \"tr\"\n    )\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use std::str;\n\n    #[test]\n    fn test_write_collapse_whitespace() {\n        for &(input, expected, preceding_whitespace) in &[\n            (\"\", \"\", false),\n            (\"  \", \" \", false),\n            (\"   \", \" \", false),\n            (\"   \", \"\", true),\n            (\" x      y  \", \" x y \", false),\n            (\" x      y  \", \"x y \", true),\n            (\" x   \\n  \\t \\n   y  \", \" x y \", false),\n            (\" x   \\n  \\t \\n   y  \", \"x y \", true),\n        ] {\n            let mut w = vec![];\n            let mut minifier = Minifier::new(&mut w);\n            minifier.preceding_whitespace = preceding_whitespace;\n            minifier\n                .write_collapse_whitespace(\n                    input.as_bytes(),\n                    reserved_entity,\n                    Some(preceding_whitespace),\n                )\n                .unwrap();\n\n            let s = str::from_utf8(&w).unwrap();\n\n            assert_eq!(expected, s);\n        }\n    }\n\n    #[test]\n    fn test_omit_tags() {\n        for &(input, expected, collapse_whitespace, preserve_comments) in &[\n            // <html>\n            (\"<html>\", \"\", true, false),\n            // Comments ignored\n            (\"<html><!-- -->\", \"\", true, false),\n            // Comments preserved\n            (\"<html>     <!-- -->    \", \"<html><!-- -->\", true, true),\n            (\"<html><!-- --></html>\", \"<html><!-- -->\", true, true),\n            (\n                \"<html><!-- --></html><!-- -->\",\n                \"<html><!-- --></html><!-- -->\",\n                true,\n                true,\n            ),\n            (\n                \"<html>    <!-- -->    </html>    <!-- -->    \",\n                \"<html><!-- --></html><!-- -->\",\n                true,\n                true,\n            ),\n            (\n                \"<html>    <!-- -->    </html>    <!-- -->    \",\n                // <body> is implicitly added to the DOM\n                \"<html><!-- --><body>        </html><!-- -->\",\n                false,\n                true,\n            ),\n            // <head>\n            (\n                \"<html>   <head>   <title>A</title>     </head>   <body><p>     B  </p> </body>\",\n                \"<title>A</title><p>B\",\n                true,\n                false,\n            ),\n            (\n                \"<html>   <head>   <title>A</title>     </head>   <body><p>     B  </p> </body>\",\n                \"<head>   <title>A</title>     </head>   <p>     B   \",\n                false,\n                false,\n            ),\n            (\n                \"<html>   <head><!-- -->   <title>A</title>     </head>   <body><p>     B  </p> </body>\",\n                \"<head><!-- --><title>A</title><p>B\",\n                true,\n                true,\n            ),\n            // <body>\n            (\"<body>\", \"\", true, false),\n            (\n                \"<body>    <script>let x = 1;</script>   \",\n                \"<body><script>let x = 1;</script>\",\n                true,\n                false,\n            ),\n            (\n                \"<body>        <style>body{margin:1em}</style>\",\n                \"<body><style>body{margin:1em}</style>\",\n                true,\n                false,\n            ),\n            (\"<body>    <p>A\", \"<p>A\", true, false),\n            (\"<body id=main>    <p>A\", \"<body id=main><p>A\", true, false),\n            // Retain whitespace, whitespace before <p>\n            (\n                \"    <body>    <p>A      \",\n                \"<body>    <p>A      \",\n                false,\n                false,\n            ),\n            // Retain whitespace, touching <p>\n            (\"<body><p>A</body>\", \"<p>A\", false, false),\n            // Comments ignored\n            (\"<body><p>A</body><!-- -->\", \"<p>A\", false, false),\n            // Comments preserved\n            (\n                \"<body><p>A</body><!-- -->\",\n                \"<body><p>A</body><!-- -->\",\n                false,\n                true,\n            ),\n            // Retain end tag if touching inline element\n            (\n                \"<p>Some text</p><button></button>\",\n                \"<p>Some text</p><button></button>\",\n                false,\n                false,\n            ),\n        ] {\n            let mut w = vec![];\n            let mut minifier = Minifier::new(&mut w);\n            minifier\n                .omit_doctype(true)\n                .collapse_whitespace(collapse_whitespace)\n                .preserve_comments(preserve_comments);\n            minifier.minify(&mut input.as_bytes()).unwrap();\n\n            let s = str::from_utf8(&w).unwrap();\n\n            assert_eq!(expected, s);\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/text/format/markdown.rs",
    "content": "use gpui::SharedString;\nuse markdown::{\n    ParseOptions,\n    mdast::{self, Node},\n};\n\nuse crate::{\n    highlighter::HighlightTheme,\n    text::{\n        document::ParsedDocument,\n        node::{\n            self, BlockNode, CodeBlock, ImageNode, InlineNode, LinkMark, NodeContext, Paragraph,\n            Span, Table, TableRow, TextMark,\n        },\n    },\n};\n\n/// Parse Markdown into a tree of nodes.\n///\n/// TODO: Remove `highlight_theme` option, this should in render stage.\npub(crate) fn parse(\n    source: &str,\n    cx: &mut NodeContext,\n    highlight_theme: &HighlightTheme,\n) -> Result<ParsedDocument, SharedString> {\n    markdown::to_mdast(&source, &ParseOptions::gfm())\n        .map(|n| ast_to_document(source, n, cx, highlight_theme))\n        .map_err(|e| e.to_string().into())\n}\n\nfn parse_table_row(table: &mut Table, node: &mdast::TableRow, cx: &mut NodeContext) {\n    let mut row = TableRow::default();\n    node.children.iter().for_each(|c| {\n        match c {\n            Node::TableCell(cell) => {\n                parse_table_cell(&mut row, cell, cx);\n            }\n            _ => {}\n        };\n    });\n    table.children.push(row);\n}\n\nfn parse_table_cell(row: &mut node::TableRow, node: &mdast::TableCell, cx: &mut NodeContext) {\n    let mut paragraph = Paragraph::default();\n    node.children.iter().for_each(|c| {\n        parse_paragraph(&mut paragraph, c, cx);\n    });\n    let table_cell = node::TableCell {\n        children: paragraph,\n        ..Default::default()\n    };\n    row.children.push(table_cell);\n}\n\nfn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node, cx: &mut NodeContext) -> String {\n    let span = node.position().map(|pos| Span {\n        start: cx.offset + pos.start.offset,\n        end: cx.offset + pos.end.offset,\n    });\n    if let Some(span) = span {\n        paragraph.set_span(span);\n    }\n\n    let mut text = String::new();\n\n    match node {\n        Node::Paragraph(val) => {\n            val.children.iter().for_each(|c| {\n                text.push_str(&parse_paragraph(paragraph, c, cx));\n            });\n        }\n        Node::Text(val) => {\n            text = val.value.clone();\n            paragraph.push_str(&val.value)\n        }\n        Node::Emphasis(val) => {\n            let mut child_paragraph = Paragraph::default();\n            for child in val.children.iter() {\n                text.push_str(&parse_paragraph(&mut child_paragraph, &child, cx));\n            }\n            paragraph.push(\n                InlineNode::new(&text).marks(vec![(0..text.len(), TextMark::default().italic())]),\n            );\n        }\n        Node::Strong(val) => {\n            let mut child_paragraph = Paragraph::default();\n            for child in val.children.iter() {\n                text.push_str(&parse_paragraph(&mut child_paragraph, &child, cx));\n            }\n            paragraph.push(\n                InlineNode::new(&text).marks(vec![(0..text.len(), TextMark::default().bold())]),\n            );\n        }\n        Node::Delete(val) => {\n            let mut child_paragraph = Paragraph::default();\n            for child in val.children.iter() {\n                text.push_str(&parse_paragraph(&mut child_paragraph, &child, cx));\n            }\n            paragraph.push(\n                InlineNode::new(&text)\n                    .marks(vec![(0..text.len(), TextMark::default().strikethrough())]),\n            );\n        }\n        Node::InlineCode(val) => {\n            text = val.value.clone();\n            paragraph.push(\n                InlineNode::new(&text).marks(vec![(0..text.len(), TextMark::default().code())]),\n            );\n        }\n        Node::Link(val) => {\n            let link_mark = Some(LinkMark {\n                url: val.url.clone().into(),\n                title: val.title.clone().map(|s| s.into()),\n                ..Default::default()\n            });\n\n            let mut child_paragraph = Paragraph::default();\n            for child in val.children.iter() {\n                text.push_str(&parse_paragraph(&mut child_paragraph, &child, cx));\n            }\n\n            // FIXME: GPUI InteractiveText does not support inline images yet.\n            // So here we push images to the paragraph directly.\n            for child in child_paragraph.children.iter_mut() {\n                if let Some(image) = child.image.as_mut() {\n                    image.link = link_mark.clone();\n                }\n\n                child.marks.push((\n                    0..child.text.len(),\n                    TextMark {\n                        link: link_mark.clone(),\n                        ..Default::default()\n                    },\n                ));\n            }\n\n            paragraph.merge(child_paragraph);\n        }\n        Node::Image(raw) => {\n            paragraph.push_image(ImageNode {\n                url: raw.url.clone().into(),\n                title: raw.title.clone().map(|t| t.into()),\n                alt: Some(raw.alt.clone().into()),\n                ..Default::default()\n            });\n        }\n        Node::InlineMath(raw) => {\n            text = raw.value.clone();\n            paragraph.push(\n                InlineNode::new(&text).marks(vec![(0..text.len(), TextMark::default().code())]),\n            );\n        }\n        Node::MdxTextExpression(raw) => {\n            text = raw.value.clone();\n            paragraph\n                .push(InlineNode::new(&text).marks(vec![(0..text.len(), TextMark::default())]));\n        }\n        Node::Html(val) => match super::html::parse(&val.value, cx) {\n            Ok(el) => {\n                if el\n                    .blocks\n                    .first()\n                    .map(|node| node.is_break())\n                    .unwrap_or(false)\n                {\n                    text = \"\\n\".to_owned();\n                    paragraph.push(InlineNode::new(&text));\n                } else {\n                    if cfg!(debug_assertions) {\n                        tracing::warn!(\"unsupported inline html tag: {:#?}\", el);\n                    }\n                }\n            }\n            Err(err) => {\n                if cfg!(debug_assertions) {\n                    tracing::warn!(\"failed parsing html: {:#?}\", err);\n                }\n\n                text.push_str(&val.value);\n            }\n        },\n        Node::FootnoteReference(foot) => {\n            let prefix = format!(\"[{}]\", foot.identifier);\n            paragraph.push(InlineNode::new(&prefix).marks(vec![(\n                0..prefix.len(),\n                TextMark {\n                    italic: true,\n                    ..Default::default()\n                },\n            )]));\n        }\n        Node::LinkReference(link) => {\n            let mut child_paragraph = Paragraph::default();\n            let mut child_text = String::new();\n            for child in link.children.iter() {\n                child_text.push_str(&parse_paragraph(&mut child_paragraph, child, cx));\n            }\n\n            let link_mark = LinkMark {\n                url: \"\".into(),\n                title: link.label.clone().map(Into::into),\n                identifier: Some(link.identifier.clone().into()),\n            };\n\n            paragraph.push(InlineNode::new(&child_text).marks(vec![(\n                0..child_text.len(),\n                TextMark {\n                    link: Some(link_mark),\n                    ..Default::default()\n                },\n            )]));\n        }\n        _ => {\n            if cfg!(debug_assertions) {\n                tracing::warn!(\"unsupported inline node: {:#?}\", node);\n            }\n        }\n    }\n\n    text\n}\n\nfn ast_to_document(\n    source: &str,\n    root: mdast::Node,\n    cx: &mut NodeContext,\n    highlight_theme: &HighlightTheme,\n) -> ParsedDocument {\n    let root = match root {\n        Node::Root(r) => r,\n        _ => panic!(\"expected root node\"),\n    };\n\n    let blocks = root\n        .children\n        .into_iter()\n        .map(|c| ast_to_node(c, cx, highlight_theme))\n        .collect();\n    ParsedDocument {\n        source: source.to_string().into(),\n        blocks,\n    }\n}\n\nfn new_span(pos: Option<markdown::unist::Position>, cx: &NodeContext) -> Option<Span> {\n    let pos = pos?;\n\n    Some(Span {\n        start: cx.offset + pos.start.offset,\n        end: cx.offset + pos.end.offset,\n    })\n}\n\nfn ast_to_node(\n    value: mdast::Node,\n    cx: &mut NodeContext,\n    highlight_theme: &HighlightTheme,\n) -> BlockNode {\n    match value {\n        Node::Root(_) => unreachable!(\"node::Root should be handled separately\"),\n        Node::Paragraph(val) => {\n            let mut paragraph = Paragraph::default();\n            val.children.iter().for_each(|c| {\n                parse_paragraph(&mut paragraph, c, cx);\n            });\n            paragraph.span = new_span(val.position, cx);\n            BlockNode::Paragraph(paragraph)\n        }\n        Node::Blockquote(val) => {\n            let children = val\n                .children\n                .into_iter()\n                .map(|c| ast_to_node(c, cx, highlight_theme))\n                .collect();\n            BlockNode::Blockquote {\n                children,\n                span: new_span(val.position, cx),\n            }\n        }\n        Node::List(list) => {\n            let children = list\n                .children\n                .into_iter()\n                .map(|c| ast_to_node(c, cx, highlight_theme))\n                .collect();\n            BlockNode::List {\n                ordered: list.ordered,\n                children,\n                span: new_span(list.position, cx),\n            }\n        }\n        Node::ListItem(val) => {\n            let children = val\n                .children\n                .into_iter()\n                .map(|c| ast_to_node(c, cx, highlight_theme))\n                .collect();\n            BlockNode::ListItem {\n                children,\n                spread: val.spread,\n                checked: val.checked,\n                span: new_span(val.position, cx),\n            }\n        }\n        Node::Break(val) => BlockNode::Break {\n            html: false,\n            span: new_span(val.position, cx),\n        },\n        Node::Code(raw) => BlockNode::CodeBlock(CodeBlock::new(\n            raw.value.into(),\n            raw.lang.map(|s| s.into()),\n            highlight_theme,\n            new_span(raw.position, cx),\n        )),\n        Node::Heading(val) => {\n            let mut paragraph = Paragraph::default();\n            val.children.iter().for_each(|c| {\n                parse_paragraph(&mut paragraph, c, cx);\n            });\n\n            BlockNode::Heading {\n                level: val.depth,\n                children: paragraph,\n                span: new_span(val.position, cx),\n            }\n        }\n        Node::Math(val) => BlockNode::CodeBlock(CodeBlock::new(\n            val.value.into(),\n            None,\n            highlight_theme,\n            new_span(val.position, cx),\n        )),\n        Node::Html(val) => match super::html::parse(&val.value, cx) {\n            Ok(el) => BlockNode::Root {\n                children: el.blocks,\n                span: new_span(val.position, cx),\n            },\n            Err(err) => {\n                if cfg!(debug_assertions) {\n                    tracing::warn!(\"error parsing html: {:#?}\", err);\n                }\n\n                BlockNode::Paragraph(Paragraph::new(val.value))\n            }\n        },\n        Node::MdxFlowExpression(val) => BlockNode::CodeBlock(CodeBlock::new(\n            val.value.into(),\n            Some(\"mdx\".into()),\n            highlight_theme,\n            new_span(val.position, cx),\n        )),\n        Node::Yaml(val) => BlockNode::CodeBlock(CodeBlock::new(\n            val.value.into(),\n            Some(\"yml\".into()),\n            highlight_theme,\n            new_span(val.position, cx),\n        )),\n        Node::Toml(val) => BlockNode::CodeBlock(CodeBlock::new(\n            val.value.into(),\n            Some(\"toml\".into()),\n            highlight_theme,\n            new_span(val.position, cx),\n        )),\n        Node::MdxJsxTextElement(val) => {\n            let mut paragraph = Paragraph::default();\n            val.children.iter().for_each(|c| {\n                parse_paragraph(&mut paragraph, c, cx);\n            });\n            paragraph.span = new_span(val.position, cx);\n            BlockNode::Paragraph(paragraph)\n        }\n        Node::MdxJsxFlowElement(val) => {\n            let mut paragraph = Paragraph::default();\n            val.children.iter().for_each(|c| {\n                parse_paragraph(&mut paragraph, c, cx);\n            });\n            paragraph.span = new_span(val.position, cx);\n            BlockNode::Paragraph(paragraph)\n        }\n        Node::ThematicBreak(val) => BlockNode::Divider {\n            span: new_span(val.position, cx),\n        },\n        Node::Table(val) => {\n            let mut table = Table::default();\n            table.column_aligns = val\n                .align\n                .clone()\n                .into_iter()\n                .map(|align| align.into())\n                .collect();\n            val.children.iter().for_each(|c| {\n                if let Node::TableRow(row) = c {\n                    parse_table_row(&mut table, row, cx);\n                }\n            });\n            table.span = new_span(val.position, cx);\n\n            BlockNode::Table(table)\n        }\n        Node::FootnoteDefinition(def) => {\n            let mut paragraph = Paragraph::default();\n            let prefix = format!(\"[{}]: \", def.identifier);\n            paragraph.push(InlineNode::new(&prefix).marks(vec![(\n                0..prefix.len(),\n                TextMark {\n                    italic: true,\n                    ..Default::default()\n                },\n            )]));\n\n            def.children.iter().for_each(|c| {\n                parse_paragraph(&mut paragraph, c, cx);\n            });\n            paragraph.span = new_span(def.position, cx);\n            BlockNode::Paragraph(paragraph)\n        }\n        Node::Definition(def) => {\n            cx.add_ref(\n                def.identifier.clone().into(),\n                LinkMark {\n                    url: def.url.clone().into(),\n                    identifier: Some(def.identifier.clone().into()),\n                    title: def.title.clone().map(Into::into),\n                },\n            );\n\n            BlockNode::Definition {\n                identifier: def.identifier.clone().into(),\n                url: def.url.clone().into(),\n                title: def.title.clone().map(|s| s.into()),\n                span: new_span(def.position, cx),\n            }\n        }\n        _ => {\n            if cfg!(debug_assertions) {\n                tracing::warn!(\"unsupported node: {:#?}\", value);\n            }\n            BlockNode::Unknown\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/text/format/mod.rs",
    "content": "pub(super) mod html;\nmod html5minify;\npub(super) mod markdown;\n"
  },
  {
    "path": "crates/ui/src/text/inline.rs",
    "content": "use std::{\n    ops::Range,\n    rc::Rc,\n    sync::{Arc, Mutex},\n};\n\nuse gpui::{\n    App, BorderStyle, Bounds, CursorStyle, Edges, Element, ElementId, GlobalElementId, Half,\n    HighlightStyle, Hitbox, HitboxBehavior, InspectorElementId, IntoElement, LayoutId,\n    MouseMoveEvent, MouseUpEvent, Pixels, Point, SharedString, StyledText, TextLayout, Window,\n    point, px, quad,\n};\n\nuse crate::{ActiveTheme, global_state::GlobalState, input::Selection, text::node::LinkMark};\n\n/// A inline element used to render a inline text and support selectable.\n///\n/// All text in TextView (including the CodeBlock) used this for text rendering.\npub(super) struct Inline {\n    id: ElementId,\n    text: SharedString,\n    links: Rc<Vec<(Range<usize>, LinkMark)>>,\n    highlights: Vec<(Range<usize>, HighlightStyle)>,\n    styled_text: StyledText,\n\n    state: Arc<Mutex<InlineState>>,\n}\n\n/// The inline text state, used RefCell to keep the selection state.\n#[derive(Debug, Default, PartialEq)]\npub(crate) struct InlineState {\n    hovered_index: Option<usize>,\n    /// The text that actually rendering, matched with selection.\n    pub(super) text: SharedString,\n    pub(super) selection: Option<Selection>,\n}\n\nimpl InlineState {\n    /// Save actually rendered text for selected text to use.\n    pub(crate) fn set_text(&mut self, text: SharedString) {\n        self.text = text;\n    }\n}\n\nimpl Inline {\n    pub(super) fn new(\n        id: impl Into<ElementId>,\n        state: Arc<Mutex<InlineState>>,\n        links: Vec<(Range<usize>, LinkMark)>,\n        highlights: Vec<(Range<usize>, HighlightStyle)>,\n    ) -> Self {\n        let text = state.lock().unwrap().text.clone();\n        Self {\n            id: id.into(),\n            links: Rc::new(links),\n            highlights,\n            text: text.clone(),\n            styled_text: StyledText::new(text),\n            state,\n        }\n    }\n\n    /// Get link at given mouse position.\n    fn link_for_position(\n        layout: &TextLayout,\n        links: &Vec<(Range<usize>, LinkMark)>,\n        position: Point<Pixels>,\n    ) -> Option<LinkMark> {\n        let offset = layout.index_for_position(position).ok()?;\n        for (range, link) in links.iter() {\n            if range.contains(&offset) {\n                return Some(link.clone());\n            }\n        }\n\n        None\n    }\n\n    /// Paint selected bounds for debug.\n    #[allow(unused)]\n    fn paint_selected_bounds(&self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App) {\n        window.paint_quad(gpui::PaintQuad {\n            bounds,\n            background: cx.theme().blue.alpha(0.01).into(),\n            corner_radii: gpui::Corners::default(),\n            border_color: gpui::transparent_black(),\n            border_style: BorderStyle::default(),\n            border_widths: gpui::Edges::all(px(0.)),\n        });\n    }\n\n    fn layout_selections(\n        &self,\n        text_layout: &TextLayout,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (bool, bool, Option<Selection>) {\n        let Some(text_view_state) = GlobalState::global(cx).text_view_state() else {\n            return (false, false, None);\n        };\n\n        let text_view_state = text_view_state.read(cx);\n        let is_selectable = text_view_state.is_selectable();\n        if !text_view_state.has_selection() {\n            return (is_selectable, false, None);\n        }\n\n        let Some((selection_start, selection_end)) = text_view_state.selection_points() else {\n            return (is_selectable, false, None);\n        };\n        let line_height = window.line_height();\n\n        // Use for debug selection bounds\n        // self.paint_selected_bounds(Bounds::from_corners(selection_start, selection_end), window, cx);\n\n        let mut selection: Option<Selection> = None;\n        let mut offset = 0;\n        let mut chars = self.text.chars().peekable();\n        while let Some(c) = chars.next() {\n            let Some(pos) = text_layout.position_for_index(offset) else {\n                offset += c.len_utf8();\n                continue;\n            };\n\n            let mut char_width = line_height.half();\n            if let Some(next_pos) = text_layout.position_for_index(offset + 1) {\n                if next_pos.y == pos.y {\n                    char_width = next_pos.x - pos.x;\n                }\n            }\n\n            if point_in_text_selection(pos, char_width, selection_start, selection_end, line_height)\n            {\n                if selection.is_none() {\n                    selection = Some((offset..offset).into());\n                }\n\n                let next_offset = offset + c.len_utf8();\n                selection.as_mut().unwrap().end = next_offset;\n            }\n\n            offset += c.len_utf8();\n        }\n\n        (true, true, selection)\n    }\n\n    /// Paint the selection background.\n    fn paint_selection(\n        selection: &Selection,\n        text_layout: &TextLayout,\n        bounds: &Bounds<Pixels>,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        let mut start = selection.start;\n        let mut end = selection.end;\n        if end < start {\n            std::mem::swap(&mut start, &mut end);\n        }\n        let Some(start_position) = text_layout.position_for_index(start) else {\n            return;\n        };\n        let Some(end_position) = text_layout.position_for_index(end) else {\n            return;\n        };\n\n        let line_height = text_layout.line_height();\n        if start_position.y == end_position.y {\n            window.paint_quad(quad(\n                Bounds::from_corners(\n                    start_position,\n                    point(end_position.x, end_position.y + line_height),\n                ),\n                px(0.),\n                cx.theme().selection,\n                Edges::default(),\n                gpui::transparent_black(),\n                BorderStyle::default(),\n            ));\n        } else {\n            window.paint_quad(quad(\n                Bounds::from_corners(\n                    start_position,\n                    point(bounds.right(), start_position.y + line_height),\n                ),\n                px(0.),\n                cx.theme().selection,\n                Edges::default(),\n                gpui::transparent_black(),\n                BorderStyle::default(),\n            ));\n\n            if end_position.y > start_position.y + line_height {\n                window.paint_quad(quad(\n                    Bounds::from_corners(\n                        point(bounds.left(), start_position.y + line_height),\n                        point(bounds.right(), end_position.y),\n                    ),\n                    px(0.),\n                    cx.theme().selection,\n                    Edges::default(),\n                    gpui::transparent_black(),\n                    BorderStyle::default(),\n                ));\n            }\n\n            window.paint_quad(quad(\n                Bounds::from_corners(\n                    point(bounds.left(), end_position.y),\n                    point(end_position.x, end_position.y + line_height),\n                ),\n                px(0.),\n                cx.theme().selection,\n                Edges::default(),\n                gpui::transparent_black(),\n                BorderStyle::default(),\n            ));\n        }\n    }\n}\n\nimpl IntoElement for Inline {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\nimpl Element for Inline {\n    type RequestLayoutState = ();\n    type PrepaintState = Hitbox;\n\n    fn id(&self) -> Option<ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        global_element_id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (LayoutId, Self::RequestLayoutState) {\n        let text_style = window.text_style();\n\n        let mut runs = Vec::new();\n        let mut ix = 0;\n        for (range, highlight) in self.highlights.iter() {\n            if ix < range.start {\n                runs.push(text_style.clone().to_run(range.start - ix));\n            }\n            runs.push(text_style.clone().highlight(*highlight).to_run(range.len()));\n            ix = range.end;\n        }\n        if ix < self.text.len() {\n            runs.push(text_style.to_run(self.text.len() - ix));\n        }\n\n        self.styled_text = StyledText::new(self.text.clone()).with_runs(runs);\n        let (layout_id, _) =\n            self.styled_text\n                .request_layout(global_element_id, inspector_id, window, cx);\n\n        (layout_id, ())\n    }\n\n    fn prepaint(\n        &mut self,\n        id: Option<&GlobalElementId>,\n        inspector_id: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        _: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::PrepaintState {\n        self.styled_text\n            .prepaint(id, inspector_id, bounds, &mut (), window, cx);\n\n        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);\n        hitbox\n    }\n\n    fn paint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        _: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        _: &mut Self::RequestLayoutState,\n        prepaint: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        let current_view = window.current_view();\n        let hitbox = prepaint;\n        let mut state = self.state.lock().unwrap();\n\n        let text_layout = self.styled_text.layout().clone();\n        self.styled_text\n            .paint(global_id, None, bounds, &mut (), &mut (), window, cx);\n\n        // layout selections\n        let (is_selectable, is_selection, selection) =\n            self.layout_selections(&text_layout, window, cx);\n\n        state.selection = selection;\n\n        if is_selection || is_selectable {\n            window.set_cursor_style(CursorStyle::IBeam, &hitbox);\n        }\n\n        // link cursor pointer\n        let mouse_position = window.mouse_position();\n        if let Some(_) = Self::link_for_position(&text_layout, &self.links, mouse_position) {\n            window.set_cursor_style(CursorStyle::PointingHand, &hitbox);\n        }\n\n        if let Some(selection) = &state.selection {\n            Self::paint_selection(selection, &text_layout, &bounds, window, cx);\n        }\n\n        // mouse move, update hovered link\n        window.on_mouse_event({\n            let hitbox = hitbox.clone();\n            let text_layout = text_layout.clone();\n            let mut hovered_index = state.hovered_index;\n            move |event: &MouseMoveEvent, phase, window, cx| {\n                if !phase.bubble() || !hitbox.is_hovered(window) {\n                    return;\n                }\n\n                let current = hovered_index;\n                let updated = text_layout.index_for_position(event.position).ok();\n                //  notify update when hovering over different links\n                if current != updated {\n                    hovered_index = updated;\n                    cx.notify(current_view);\n                }\n            }\n        });\n\n        if !is_selection {\n            // click to open link\n            window.on_mouse_event({\n                let links = self.links.clone();\n                let text_layout = text_layout.clone();\n                let hitbox = hitbox.clone();\n\n                move |event: &MouseUpEvent, phase, window, cx| {\n                    if !phase.bubble() || !hitbox.is_hovered(window) {\n                        return;\n                    }\n\n                    if let Some(link) =\n                        Self::link_for_position(&text_layout, &links, event.position)\n                    {\n                        cx.stop_propagation();\n                        cx.open_url(&link.url);\n                    }\n                }\n            });\n        }\n    }\n}\n\n/// Check if a `pos` is within a `bounds`, considering multi-line selections.\nfn point_in_text_selection(\n    pos: Point<Pixels>,\n    char_width: Pixels,\n    selection_start: Point<Pixels>,\n    selection_end: Point<Pixels>,\n    line_height: Pixels,\n) -> bool {\n    let point_in_line = |point: Point<Pixels>| point.y >= pos.y && point.y < pos.y + line_height;\n    let top = selection_start.y.min(selection_end.y);\n    let bottom = selection_start.y.max(selection_end.y);\n    let x = pos.x + char_width.half();\n\n    // Out of the vertical bounds\n    if pos.y + line_height <= top || pos.y > bottom {\n        return false;\n    }\n\n    // Treat the selection as single-line when both drag points fall within the\n    // same rendered line, even if their y coordinates differ inside that line.\n    if point_in_line(selection_start) && point_in_line(selection_end) {\n        let left = selection_start.x.min(selection_end.x);\n        let right = selection_start.x.max(selection_end.x);\n        return x >= left && x <= right;\n    }\n\n    let (top_point, bottom_point) = if selection_start.y < selection_end.y {\n        (selection_start, selection_end)\n    } else {\n        (selection_end, selection_start)\n    };\n    let is_top_line = point_in_line(top_point);\n    let is_bottom_line = point_in_line(bottom_point);\n\n    if is_top_line {\n        return x >= top_point.x;\n    } else if is_bottom_line {\n        return x <= bottom_point.x;\n    } else {\n        return true;\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::point_in_text_selection;\n    use gpui::{point, px};\n\n    #[test]\n    fn test_point_in_text_selection() {\n        let line_height = px(20.);\n        let char_width = px(10.);\n        let start = point(px(50.), px(50.));\n        let end = point(px(150.), px(150.));\n\n        // First line but haft line height, true\n        // | p --------|\n        // | selection |\n        // |-----------|\n        assert!(point_in_text_selection(\n            point(px(50.), px(40.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n\n        // First line in selection, true\n        // | p --------|\n        // | selection |\n        // |-----------|\n        assert!(point_in_text_selection(\n            point(px(50.), px(50.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n        // First line, but left out of selection, false\n        // p |-----------|\n        //   | selection |\n        //   |-----------|\n        assert!(!point_in_text_selection(\n            point(px(40.), px(50.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n        // First line but right out of selection, true\n        // |-----------| p\n        // | selection |\n        // |-----------|\n        assert!(point_in_text_selection(\n            point(px(160.), px(50.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n\n        // Middle line in selection, true\n        // |-----------|\n        // |     p     |\n        // |-----------|\n        assert!(point_in_text_selection(\n            point(px(100.), px(70.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n        // Middle line, but left out of selection, true\n        //   |-----------|\n        // p | selection |\n        //   |-----------|\n        assert!(point_in_text_selection(\n            point(px(40.), px(70.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n        // Middle line, but right out of selection, true\n        // |-----------|\n        // | selection | p\n        // |-----------|\n        assert!(point_in_text_selection(\n            point(px(160.), px(70.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n\n        // Last line in selection, true\n        // |-----------|\n        // | selection |\n        // |------- p -|\n        assert!(point_in_text_selection(\n            point(px(100.), px(140.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n        // Last line, but left out of selection, true\n        //\n        //   |-----------|\n        //   | selection |\n        // p |-----------|\n        assert!(point_in_text_selection(\n            point(px(40.), px(140.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n        // Last line, but right out of selection, false\n        // |-----------|\n        // | selection |\n        // |-----------| p\n        assert!(!point_in_text_selection(\n            point(px(160.), px(140.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n\n        // Out of vertical bounds (top), false\n        //       p\n        // |-----------|\n        // | selection |\n        // |-----------|\n        assert!(!point_in_text_selection(\n            point(px(100.), px(20.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n        // Out of vertical bounds (bottom), false\n        // |-----------|\n        // | selection |\n        // |-----------|\n        //       p\n        assert!(!point_in_text_selection(\n            point(px(100.), px(160.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n    }\n\n    #[test]\n    fn test_point_in_text_selection_reversed_drag_direction() {\n        let line_height = px(20.);\n        let char_width = px(10.);\n\n        // Mouse down on lower line then drag upward to x=150.\n        // Top line should follow current mouse x, bottom line should keep anchor x.\n        let start = point(px(80.), px(150.));\n        let end = point(px(150.), px(50.));\n\n        // On top line, selection starts from top cursor x (150), so x=140 should be excluded.\n        assert!(!point_in_text_selection(\n            point(px(140.), px(50.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n        assert!(point_in_text_selection(\n            point(px(150.), px(50.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n\n        // On bottom line, selection ends at anchor x (80), so x=90 should be excluded.\n        assert!(point_in_text_selection(\n            point(px(75.), px(140.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n        assert!(!point_in_text_selection(\n            point(px(80.), px(140.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n    }\n\n    #[test]\n    fn test_point_in_text_selection_same_visual_line_with_different_y() {\n        let line_height = px(20.);\n        let char_width = px(10.);\n        let start = point(px(100.), px(55.));\n        let end = point(px(60.), px(58.));\n\n        assert!(!point_in_text_selection(\n            point(px(40.), px(50.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n        assert!(point_in_text_selection(\n            point(px(70.), px(50.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n        assert!(!point_in_text_selection(\n            point(px(110.), px(50.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n    }\n\n    #[test]\n    fn test_point_in_text_selection_same_visual_line_with_reversed_y() {\n        let line_height = px(20.);\n        let char_width = px(10.);\n        let start = point(px(60.), px(58.));\n        let end = point(px(100.), px(55.));\n\n        assert!(!point_in_text_selection(\n            point(px(40.), px(50.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n        assert!(point_in_text_selection(\n            point(px(70.), px(50.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n        assert!(!point_in_text_selection(\n            point(px(110.), px(50.)),\n            char_width,\n            start,\n            end,\n            line_height\n        ));\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/text/mod.rs",
    "content": "mod document;\nmod format;\nmod inline;\nmod node;\nmod state;\nmod style;\nmod text_view;\nmod utils;\n\nuse gpui::{App, ElementId, IntoElement, RenderOnce, SharedString, Window};\npub use state::*;\npub use style::*;\npub use text_view::*;\n\npub(crate) fn init(cx: &mut App) {\n    state::init(cx);\n}\n\n/// Create a new markdown text view with code location as id.\n#[track_caller]\npub fn markdown(source: impl Into<SharedString>) -> TextView {\n    let id: ElementId = ElementId::CodeLocation(*std::panic::Location::caller());\n    TextView::markdown(id, source)\n}\n\n/// Create a new html text view with code location as id.\n#[track_caller]\npub fn html(source: impl Into<SharedString>) -> TextView {\n    let id: ElementId = ElementId::CodeLocation(*std::panic::Location::caller());\n    TextView::html(id, source)\n}\n\n#[derive(IntoElement, Clone)]\npub enum Text {\n    String(SharedString),\n    TextView(Box<TextView>),\n}\n\nimpl From<SharedString> for Text {\n    fn from(s: SharedString) -> Self {\n        Self::String(s)\n    }\n}\n\nimpl From<&str> for Text {\n    fn from(s: &str) -> Self {\n        Self::String(SharedString::from(s.to_string()))\n    }\n}\n\nimpl From<String> for Text {\n    fn from(s: String) -> Self {\n        Self::String(s.into())\n    }\n}\n\nimpl From<TextView> for Text {\n    fn from(e: TextView) -> Self {\n        Self::TextView(Box::new(e))\n    }\n}\n\nimpl Text {\n    /// Set the style for [`TextView`].\n    ///\n    /// Do nothing if this is `String`.\n    pub fn style(self, style: TextViewStyle) -> Self {\n        match self {\n            Self::String(s) => Self::String(s),\n            Self::TextView(e) => Self::TextView(Box::new(e.style(style))),\n        }\n    }\n\n    /// Get the text content.\n    pub(crate) fn get_text(&self, cx: &App) -> SharedString {\n        match self {\n            Self::String(s) => s.clone(),\n            Self::TextView(view) => {\n                if let Some(state) = &view.state {\n                    state.read(cx).source()\n                } else {\n                    SharedString::default()\n                }\n            }\n        }\n    }\n}\n\nimpl RenderOnce for Text {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        match self {\n            Self::String(s) => s.into_any_element(),\n            Self::TextView(e) => e.into_any_element(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/text/node.rs",
    "content": "use std::{\n    collections::HashMap,\n    ops::Range,\n    sync::{Arc, Mutex},\n};\n\nuse gpui::{\n    AnyElement, App, DefiniteLength, Div, ElementId, FontStyle, FontWeight, Half, HighlightStyle,\n    InteractiveElement as _, IntoElement, Length, ObjectFit, ParentElement, SharedString,\n    SharedUri, StatefulInteractiveElement, Styled, StyledImage as _, Window, div, img,\n    prelude::FluentBuilder as _, px, relative, rems,\n};\nuse markdown::mdast;\nuse ropey::Rope;\n\nuse crate::{\n    ActiveTheme as _, Icon, IconName, StyledExt, h_flex,\n    highlighter::{HighlightTheme, SyntaxHighlighter},\n    text::{\n        CodeBlockActionsFn,\n        document::NodeRenderOptions,\n        inline::{Inline, InlineState},\n    },\n    tooltip::Tooltip,\n    v_flex,\n};\n\nuse super::{TextViewStyle, utils::list_item_prefix};\n\n/// The block-level nodes.\n#[derive(Debug, Clone, PartialEq)]\npub(crate) enum BlockNode {\n    /// Something like a Div container in HTML.\n    Root {\n        children: Vec<BlockNode>,\n        span: Option<Span>,\n    },\n    Paragraph(Paragraph),\n    Heading {\n        level: u8,\n        children: Paragraph,\n        span: Option<Span>,\n    },\n    Blockquote {\n        children: Vec<BlockNode>,\n        span: Option<Span>,\n    },\n    List {\n        /// Only contains ListItem, others will be ignored\n        children: Vec<BlockNode>,\n        ordered: bool,\n        span: Option<Span>,\n    },\n    ListItem {\n        children: Vec<BlockNode>,\n        spread: bool,\n        /// Whether the list item is checked, if None, it's not a checkbox\n        checked: Option<bool>,\n        span: Option<Span>,\n    },\n    CodeBlock(CodeBlock),\n    Table(Table),\n    Break {\n        html: bool,\n        span: Option<Span>,\n    },\n    Divider {\n        span: Option<Span>,\n    },\n    /// Use for to_markdown get raw definition\n    Definition {\n        identifier: SharedString,\n        url: SharedString,\n        title: Option<SharedString>,\n        span: Option<Span>,\n    },\n    Unknown,\n}\n\nimpl BlockNode {\n    pub(super) fn is_list_item(&self) -> bool {\n        matches!(self, Self::ListItem { .. })\n    }\n\n    pub(super) fn is_break(&self) -> bool {\n        matches!(self, Self::Break { .. })\n    }\n\n    /// Combine all children, omitting the empt parent nodes.\n    pub(super) fn compact(self) -> BlockNode {\n        match self {\n            Self::Root { mut children, .. } if children.len() == 1 => children.remove(0).compact(),\n            _ => self,\n        }\n    }\n\n    /// Get the span of the node.\n    pub(super) fn span(&self) -> Option<Span> {\n        match self {\n            BlockNode::Root { span, .. } => *span,\n            BlockNode::Paragraph(paragraph) => paragraph.span,\n            BlockNode::Heading { span, .. } => *span,\n            BlockNode::Blockquote { span, .. } => *span,\n            BlockNode::List { span, .. } => *span,\n            BlockNode::ListItem { span, .. } => *span,\n            BlockNode::CodeBlock(code_block) => code_block.span,\n            BlockNode::Table(table) => table.span,\n            BlockNode::Break { span, .. } => *span,\n            BlockNode::Divider { span, .. } => *span,\n            BlockNode::Definition { span, .. } => *span,\n            BlockNode::Unknown { .. } => None,\n        }\n    }\n\n    pub(super) fn selected_text(&self) -> String {\n        let mut text = String::new();\n        match self {\n            BlockNode::Root { children, .. } => {\n                let mut block_text = String::new();\n                for c in children.iter() {\n                    block_text.push_str(&c.selected_text());\n                }\n                if !block_text.is_empty() {\n                    text.push_str(&block_text);\n                    text.push('\\n');\n                }\n            }\n            BlockNode::Paragraph(paragraph) => {\n                let mut block_text = String::new();\n                block_text.push_str(&paragraph.selected_text());\n                if !block_text.is_empty() {\n                    text.push_str(&block_text);\n                    text.push('\\n');\n                }\n            }\n            BlockNode::Heading { children, .. } => {\n                let mut block_text = String::new();\n                block_text.push_str(&children.selected_text());\n                if !block_text.is_empty() {\n                    text.push_str(&block_text);\n                    text.push('\\n');\n                }\n            }\n            BlockNode::List { children, .. } => {\n                for c in children.iter() {\n                    text.push_str(&c.selected_text());\n                }\n            }\n            BlockNode::ListItem { children, .. } => {\n                for c in children.iter() {\n                    text.push_str(&c.selected_text());\n                }\n            }\n            BlockNode::Blockquote { children, .. } => {\n                let mut block_text = String::new();\n                for c in children.iter() {\n                    block_text.push_str(&c.selected_text());\n                }\n\n                if !block_text.is_empty() {\n                    text.push_str(&block_text);\n                    text.push('\\n');\n                }\n            }\n            BlockNode::Table(table) => {\n                let mut block_text = String::new();\n                for row in table.children.iter() {\n                    let mut row_texts = vec![];\n                    for cell in row.children.iter() {\n                        row_texts.push(cell.children.selected_text());\n                    }\n                    if !row_texts.is_empty() {\n                        block_text.push_str(&row_texts.join(\" \"));\n                        block_text.push('\\n');\n                    }\n                }\n\n                if !block_text.is_empty() {\n                    text.push_str(&block_text);\n                    text.push('\\n');\n                }\n            }\n            BlockNode::CodeBlock(code_block) => {\n                let block_text = code_block.selected_text();\n                if !block_text.is_empty() {\n                    text.push_str(&block_text);\n                    text.push('\\n');\n                }\n            }\n            BlockNode::Definition { .. }\n            | BlockNode::Break { .. }\n            | BlockNode::Divider { .. }\n            | BlockNode::Unknown { .. } => {}\n        }\n\n        text\n    }\n}\n\n#[allow(unused)]\n#[derive(Debug, Default, Clone, PartialEq)]\npub struct LinkMark {\n    pub url: SharedString,\n    /// Optional identifier for footnotes.\n    pub identifier: Option<SharedString>,\n    pub title: Option<SharedString>,\n}\n\n#[derive(Debug, Default, Clone, PartialEq)]\npub struct TextMark {\n    pub bold: bool,\n    pub italic: bool,\n    pub strikethrough: bool,\n    pub code: bool,\n    pub link: Option<LinkMark>,\n}\n\nimpl TextMark {\n    pub fn bold(mut self) -> Self {\n        self.bold = true;\n        self\n    }\n\n    pub fn italic(mut self) -> Self {\n        self.italic = true;\n        self\n    }\n\n    pub fn strikethrough(mut self) -> Self {\n        self.strikethrough = true;\n        self\n    }\n\n    pub fn code(mut self) -> Self {\n        self.code = true;\n        self\n    }\n\n    pub fn link(mut self, link: impl Into<LinkMark>) -> Self {\n        self.link = Some(link.into());\n        self\n    }\n\n    pub fn merge(&mut self, other: TextMark) {\n        self.bold |= other.bold;\n        self.italic |= other.italic;\n        self.strikethrough |= other.strikethrough;\n        self.code |= other.code;\n        if let Some(link) = other.link {\n            self.link = Some(link);\n        }\n    }\n}\n\n/// The bytes\n#[derive(Debug, Default, Copy, Clone, PartialEq)]\npub struct Span {\n    pub start: usize,\n    pub end: usize,\n}\n\nimpl From<Span> for ElementId {\n    fn from(value: Span) -> Self {\n        ElementId::Name(format!(\"md-{}:{}\", value.start, value.end).into())\n    }\n}\n\n#[allow(unused)]\n#[derive(Debug, Default, Clone)]\npub struct ImageNode {\n    pub url: SharedUri,\n    pub link: Option<LinkMark>,\n    pub title: Option<SharedString>,\n    pub alt: Option<SharedString>,\n    pub width: Option<DefiniteLength>,\n    pub height: Option<DefiniteLength>,\n}\n\nimpl ImageNode {\n    pub fn title(&self) -> String {\n        self.title\n            .clone()\n            .unwrap_or_else(|| self.alt.clone().unwrap_or_default())\n            .to_string()\n    }\n}\n\nimpl PartialEq for ImageNode {\n    fn eq(&self, other: &Self) -> bool {\n        self.url == other.url\n            && self.link == other.link\n            && self.title == other.title\n            && self.alt == other.alt\n            && self.width == other.width\n            && self.height == other.height\n    }\n}\n\n#[derive(Default, Clone, Debug)]\npub(crate) struct InlineNode {\n    /// The text content.\n    pub(crate) text: SharedString,\n    pub(crate) image: Option<ImageNode>,\n    /// The text styles, each tuple contains the range of the text and the style.\n    pub(crate) marks: Vec<(Range<usize>, TextMark)>,\n\n    state: Arc<Mutex<InlineState>>,\n}\n\nimpl PartialEq for InlineNode {\n    fn eq(&self, other: &Self) -> bool {\n        self.text == other.text && self.image == other.image && self.marks == other.marks\n    }\n}\n\nimpl InlineNode {\n    pub(crate) fn new(text: impl Into<SharedString>) -> Self {\n        Self {\n            text: text.into(),\n            image: None,\n            marks: vec![],\n            state: Arc::new(Mutex::new(InlineState::default())),\n        }\n    }\n\n    pub(crate) fn image(image: ImageNode) -> Self {\n        let mut this = Self::new(\"\");\n        this.image = Some(image);\n        this\n    }\n\n    pub(crate) fn marks(mut self, marks: Vec<(Range<usize>, TextMark)>) -> Self {\n        self.marks = marks;\n        self\n    }\n}\n\n/// The paragraph element, contains multiple text nodes.\n///\n/// Unlike other Element, this is cloneable, because it is used in the Node AST.\n/// We are keep the selection state inside this AST Nodes.\n#[derive(Debug, Clone, Default)]\npub(crate) struct Paragraph {\n    pub(super) span: Option<Span>,\n    pub(super) children: Vec<InlineNode>,\n    /// The link references in this paragraph, used for reference links.\n    ///\n    /// The key is the identifier, the value is the url.\n    pub(super) link_refs: HashMap<SharedString, SharedString>,\n\n    pub(crate) state: Arc<Mutex<InlineState>>,\n}\n\nimpl PartialEq for Paragraph {\n    fn eq(&self, other: &Self) -> bool {\n        self.span == other.span\n            && self.children == other.children\n            && self.link_refs == other.link_refs\n    }\n}\n\nimpl Paragraph {\n    pub(crate) fn new(text: String) -> Self {\n        Self {\n            span: None,\n            children: vec![InlineNode::new(&text)],\n            link_refs: HashMap::new(),\n            state: Arc::new(Mutex::new(InlineState::default())),\n        }\n    }\n\n    pub(super) fn selected_text(&self) -> String {\n        let mut text = String::new();\n\n        for c in self.children.iter() {\n            let state = c.state.lock().unwrap();\n            if let Some(selection) = &state.selection {\n                let part_text = state.text.clone();\n                text.push_str(&part_text[selection.start..selection.end]);\n            }\n        }\n\n        let state = self.state.lock().unwrap();\n        if let Some(selection) = &state.selection {\n            let all_text = state.text.clone();\n            text.push_str(&all_text[selection.start..selection.end]);\n        }\n\n        text\n    }\n}\n\n#[derive(Debug, Clone, Default, PartialEq)]\npub(crate) struct Table {\n    pub(crate) children: Vec<TableRow>,\n    pub(crate) column_aligns: Vec<ColumnumnAlign>,\n    pub(crate) span: Option<Span>,\n}\n\nimpl Table {\n    pub(crate) fn column_align(&self, index: usize) -> ColumnumnAlign {\n        self.column_aligns.get(index).copied().unwrap_or_default()\n    }\n}\n\n#[derive(Debug, Default, Copy, Clone, PartialEq)]\npub(crate) enum ColumnumnAlign {\n    #[default]\n    Left,\n    Center,\n    Right,\n}\n\nimpl From<mdast::AlignKind> for ColumnumnAlign {\n    fn from(value: mdast::AlignKind) -> Self {\n        match value {\n            mdast::AlignKind::None => ColumnumnAlign::Left,\n            mdast::AlignKind::Left => ColumnumnAlign::Left,\n            mdast::AlignKind::Center => ColumnumnAlign::Center,\n            mdast::AlignKind::Right => ColumnumnAlign::Right,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Default, PartialEq)]\npub(crate) struct TableRow {\n    pub children: Vec<TableCell>,\n}\n\n#[derive(Debug, Clone, Default, PartialEq)]\npub(crate) struct TableCell {\n    pub children: Paragraph,\n    pub width: Option<DefiniteLength>,\n}\n\nimpl Paragraph {\n    pub(crate) fn take(&mut self) -> Paragraph {\n        std::mem::replace(\n            self,\n            Paragraph {\n                span: None,\n                children: vec![],\n                link_refs: Default::default(),\n                state: Arc::new(Mutex::new(InlineState::default())),\n            },\n        )\n    }\n\n    pub(crate) fn is_image(&self) -> bool {\n        false\n    }\n\n    pub(crate) fn set_span(&mut self, span: Span) {\n        self.span = Some(span);\n    }\n\n    pub(crate) fn push_str(&mut self, text: &str) {\n        self.children.push(\n            InlineNode::new(text.to_string()).marks(vec![(0..text.len(), TextMark::default())]),\n        );\n    }\n\n    pub(crate) fn push(&mut self, text: InlineNode) {\n        self.children.push(text);\n    }\n\n    pub(crate) fn push_image(&mut self, image: ImageNode) {\n        self.children.push(InlineNode::image(image));\n    }\n\n    pub(crate) fn is_empty(&self) -> bool {\n        self.children.is_empty()\n            || self\n                .children\n                .iter()\n                .all(|node| node.text.is_empty() && node.image.is_none())\n    }\n\n    /// Return length of children text.\n    pub(crate) fn text_len(&self) -> usize {\n        self.children\n            .iter()\n            .map(|node| node.text.len())\n            .sum::<usize>()\n    }\n\n    pub(crate) fn merge(&mut self, other: Self) {\n        self.children.extend(other.children);\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct CodeBlock {\n    lang: Option<SharedString>,\n    styles: Vec<(Range<usize>, HighlightStyle)>,\n    state: Arc<Mutex<InlineState>>,\n    pub span: Option<Span>,\n}\n\nimpl PartialEq for CodeBlock {\n    fn eq(&self, other: &Self) -> bool {\n        self.lang == other.lang && self.styles == other.styles\n    }\n}\n\nimpl CodeBlock {\n    /// Get the language of the code block.\n    pub fn lang(&self) -> Option<SharedString> {\n        self.lang.clone()\n    }\n\n    /// Get the code content of the code block.\n    pub fn code(&self) -> SharedString {\n        self.state.lock().unwrap().text.clone()\n    }\n\n    pub(crate) fn new(\n        code: SharedString,\n        lang: Option<SharedString>,\n        highlight_theme: &HighlightTheme,\n        span: Option<impl Into<Span>>,\n    ) -> Self {\n        let mut styles = vec![];\n        if let Some(lang) = &lang {\n            let mut highlighter = SyntaxHighlighter::new(&lang);\n            highlighter.update(None, &Rope::from_str(code.as_str()), None);\n            styles = highlighter.styles(&(0..code.len()), highlight_theme);\n        };\n\n        let state = Arc::new(Mutex::new(InlineState::default()));\n        state.lock().unwrap().set_text(code);\n\n        Self {\n            lang,\n            styles,\n            state,\n            span: span.map(|s| s.into()),\n        }\n    }\n\n    pub(super) fn selected_text(&self) -> String {\n        let mut text = String::new();\n        let state = self.state.lock().unwrap();\n        if let Some(selection) = &state.selection {\n            let part_text = state.text.clone();\n            text.push_str(&part_text[selection.start..selection.end]);\n        }\n        text\n    }\n\n    fn render(\n        &self,\n        options: &NodeRenderOptions,\n        node_cx: &NodeContext,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> AnyElement {\n        let style = &node_cx.style;\n\n        div()\n            .when(!options.is_last, |this| this.pb(style.paragraph_gap))\n            .child(\n                div()\n                    .id((\"codeblock\", options.ix))\n                    .p_3()\n                    .rounded(cx.theme().radius)\n                    .bg(cx.theme().muted)\n                    .font_family(cx.theme().mono_font_family.clone())\n                    .text_size(cx.theme().mono_font_size)\n                    .relative()\n                    .refine_style(&style.code_block)\n                    .child(Inline::new(\n                        \"code\",\n                        self.state.clone(),\n                        vec![],\n                        self.styles.clone(),\n                    ))\n                    .when_some(node_cx.code_block_actions.clone(), |this, actions| {\n                        this.child(\n                            div()\n                                .id(\"actions\")\n                                .absolute()\n                                .top_2()\n                                .right_2()\n                                .bg(cx.theme().muted)\n                                .rounded(cx.theme().radius)\n                                .child(actions(&self, window, cx)),\n                        )\n                    }),\n            )\n            .into_any_element()\n    }\n}\n\n/// A context for rendering nodes, contains link references.\n#[derive(Default, Clone)]\npub(crate) struct NodeContext {\n    /// The byte offset of the node in the original markdown text.\n    /// Used for incremental updates.\n    pub(crate) offset: usize,\n    pub(crate) link_refs: HashMap<SharedString, LinkMark>,\n    pub(crate) style: TextViewStyle,\n    pub(crate) code_block_actions: Option<Arc<CodeBlockActionsFn>>,\n}\n\nimpl NodeContext {\n    pub(super) fn add_ref(&mut self, identifier: SharedString, link: LinkMark) {\n        self.link_refs.insert(identifier, link);\n    }\n}\n\nimpl PartialEq for NodeContext {\n    fn eq(&self, other: &Self) -> bool {\n        self.link_refs == other.link_refs && self.style == other.style\n        // Note: code_block_buttons is intentionally not compared (closures can't be compared)\n    }\n}\n\nimpl Paragraph {\n    fn render(\n        &self,\n        node_cx: &NodeContext,\n        _window: &mut Window,\n        cx: &mut App,\n    ) -> impl IntoElement {\n        let span = self.span;\n        let children = &self.children;\n\n        let mut child_nodes: Vec<AnyElement> = vec![];\n\n        let mut text = String::new();\n        let mut highlights: Vec<(Range<usize>, HighlightStyle)> = vec![];\n        let mut links: Vec<(Range<usize>, LinkMark)> = vec![];\n        let mut offset = 0;\n\n        let mut ix = 0;\n        for inline_node in children {\n            let text_len = inline_node.text.len();\n            text.push_str(&inline_node.text);\n\n            if let Some(image) = &inline_node.image {\n                if text.len() > 0 {\n                    inline_node\n                        .state\n                        .lock()\n                        .unwrap()\n                        .set_text(text.clone().into());\n                    child_nodes.push(\n                        Inline::new(\n                            ix,\n                            inline_node.state.clone(),\n                            links.clone(),\n                            highlights.clone(),\n                        )\n                        .into_any_element(),\n                    );\n                }\n                child_nodes.push(\n                    img(image.url.clone())\n                        .id(ix)\n                        .object_fit(ObjectFit::Contain)\n                        .max_w(relative(1.))\n                        .when_some(image.width, |this, width| this.w(width))\n                        .when_some(image.link.clone(), |this, link| {\n                            let title = image.title();\n                            this.cursor_pointer()\n                                .tooltip(move |window, cx| {\n                                    Tooltip::new(title.clone()).build(window, cx)\n                                })\n                                .on_click(move |_, _, cx| {\n                                    cx.stop_propagation();\n                                    cx.open_url(&link.url);\n                                })\n                        })\n                        .into_any_element(),\n                );\n\n                text.clear();\n                links.clear();\n                highlights.clear();\n                offset = 0;\n            } else {\n                let mut node_highlights = vec![];\n                for (range, style) in &inline_node.marks {\n                    let inner_range = (offset + range.start)..(offset + range.end);\n\n                    let mut highlight = HighlightStyle::default();\n                    if style.bold {\n                        highlight.font_weight = Some(FontWeight::BOLD);\n                    }\n                    if style.italic {\n                        highlight.font_style = Some(FontStyle::Italic);\n                    }\n                    if style.strikethrough {\n                        highlight.strikethrough = Some(gpui::StrikethroughStyle {\n                            thickness: gpui::px(1.),\n                            ..Default::default()\n                        });\n                    }\n                    if style.code {\n                        highlight.background_color = Some(cx.theme().accent);\n                    }\n\n                    if let Some(mut link_mark) = style.link.clone() {\n                        highlight.color = Some(cx.theme().link);\n                        highlight.underline = Some(gpui::UnderlineStyle {\n                            thickness: gpui::px(1.),\n                            ..Default::default()\n                        });\n\n                        // convert link references, replace link\n                        if let Some(identifier) = link_mark.identifier.as_ref() {\n                            if let Some(mark) = node_cx.link_refs.get(identifier) {\n                                link_mark = mark.clone();\n                            }\n                        }\n\n                        links.push((inner_range.clone(), link_mark));\n                    }\n\n                    node_highlights.push((inner_range, highlight));\n                }\n\n                highlights = gpui::combine_highlights(highlights, node_highlights).collect();\n                offset += text_len;\n            }\n            ix += 1;\n        }\n\n        // Add the last text node\n        if text.len() > 0 {\n            self.state.lock().unwrap().set_text(text.into());\n            child_nodes\n                .push(Inline::new(ix, self.state.clone(), links, highlights).into_any_element());\n        }\n\n        div().id(span.unwrap_or_default()).children(child_nodes)\n    }\n}\n\nimpl Paragraph {\n    fn to_markdown(&self) -> String {\n        let mut text = self\n            .children\n            .iter()\n            .map(|text_node| {\n                let mut text = text_node.text.to_string();\n                for (range, style) in &text_node.marks {\n                    if style.bold {\n                        text = format!(\"**{}**\", &text_node.text[range.clone()]);\n                    }\n                    if style.italic {\n                        text = format!(\"*{}*\", &text_node.text[range.clone()]);\n                    }\n                    if style.strikethrough {\n                        text = format!(\"~~{}~~\", &text_node.text[range.clone()]);\n                    }\n                    if style.code {\n                        text = format!(\"`{}`\", &text_node.text[range.clone()]);\n                    }\n                    if let Some(link) = &style.link {\n                        text = format!(\"[{}]({})\", &text_node.text[range.clone()], link.url);\n                    }\n                }\n\n                if let Some(image) = &text_node.image {\n                    let alt = image.alt.clone().unwrap_or_default();\n                    let title = image\n                        .title\n                        .clone()\n                        .map_or(String::new(), |t| format!(\" \\\"{}\\\"\", t));\n                    text.push_str(&format!(\"![{}]({}{})\", alt, image.url, title))\n                }\n\n                text\n            })\n            .collect::<Vec<_>>()\n            .join(\"\");\n\n        text.push_str(\"\\n\\n\");\n        text\n    }\n}\n\nimpl BlockNode {\n    /// Converts the node to markdown format.\n    ///\n    /// This is used to generate markdown for test.\n    #[allow(dead_code)]\n    pub(crate) fn to_markdown(&self) -> String {\n        match self {\n            BlockNode::Root { children, .. } => children\n                .iter()\n                .map(|child| child.to_markdown())\n                .collect::<Vec<_>>()\n                .join(\"\\n\\n\"),\n            BlockNode::Paragraph(paragraph) => paragraph.to_markdown(),\n            BlockNode::Heading {\n                level, children, ..\n            } => {\n                let hashes = \"#\".repeat(*level as usize);\n                format!(\"{} {}\", hashes, children.to_markdown())\n            }\n            BlockNode::Blockquote { children, .. } => {\n                let content = children\n                    .iter()\n                    .map(|child| child.to_markdown())\n                    .collect::<Vec<_>>()\n                    .join(\"\\n\\n\");\n\n                content\n                    .lines()\n                    .map(|line| format!(\"> {}\", line))\n                    .collect::<Vec<_>>()\n                    .join(\"\\n\")\n            }\n            BlockNode::List {\n                children, ordered, ..\n            } => children\n                .iter()\n                .enumerate()\n                .map(|(i, child)| {\n                    let prefix = if *ordered {\n                        format!(\"{}. \", i + 1)\n                    } else {\n                        \"- \".to_string()\n                    };\n                    format!(\"{}{}\", prefix, child.to_markdown())\n                })\n                .collect::<Vec<_>>()\n                .join(\"\\n\"),\n            BlockNode::ListItem {\n                children, checked, ..\n            } => {\n                let checkbox = if let Some(checked) = checked {\n                    if *checked { \"[x] \" } else { \"[ ] \" }\n                } else {\n                    \"\"\n                };\n                format!(\n                    \"{}{}\",\n                    checkbox,\n                    children\n                        .iter()\n                        .map(|child| child.to_markdown())\n                        .collect::<Vec<_>>()\n                        .join(\"\\n\")\n                )\n            }\n            BlockNode::CodeBlock(code_block) => {\n                format!(\n                    \"```{}\\n{}\\n```\",\n                    code_block.lang.clone().unwrap_or_default(),\n                    code_block.code()\n                )\n            }\n            BlockNode::Table(table) => {\n                let header = table\n                    .children\n                    .first()\n                    .map(|row| {\n                        row.children\n                            .iter()\n                            .map(|cell| cell.children.to_markdown())\n                            .collect::<Vec<_>>()\n                            .join(\" | \")\n                    })\n                    .unwrap_or_default();\n                let alignments = table\n                    .column_aligns\n                    .iter()\n                    .map(|align| {\n                        match align {\n                            ColumnumnAlign::Left => \":--\",\n                            ColumnumnAlign::Center => \":-:\",\n                            ColumnumnAlign::Right => \"--:\",\n                        }\n                        .to_string()\n                    })\n                    .collect::<Vec<_>>()\n                    .join(\" | \");\n                let rows = table\n                    .children\n                    .iter()\n                    .skip(1)\n                    .map(|row| {\n                        row.children\n                            .iter()\n                            .map(|cell| cell.children.to_markdown())\n                            .collect::<Vec<_>>()\n                            .join(\" | \")\n                    })\n                    .collect::<Vec<_>>()\n                    .join(\"\\n\");\n                format!(\"{}\\n{}\\n{}\", header, alignments, rows)\n            }\n            BlockNode::Break { html, .. } => {\n                if *html {\n                    \"<br>\".to_string()\n                } else {\n                    \"\\n\".to_string()\n                }\n            }\n            BlockNode::Divider { .. } => \"---\".to_string(),\n            BlockNode::Definition {\n                identifier,\n                url,\n                title,\n                ..\n            } => {\n                if let Some(title) = title {\n                    format!(\"[{}]: {} \\\"{}\\\"\", identifier, url, title)\n                } else {\n                    format!(\"[{}]: {}\", identifier, url)\n                }\n            }\n            BlockNode::Unknown { .. } => \"\".to_string(),\n        }\n        .trim()\n        .to_string()\n    }\n}\n\nimpl BlockNode {\n    fn render_list_item(\n        item: &BlockNode,\n        ix: usize,\n        options: NodeRenderOptions,\n        node_cx: &NodeContext,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> AnyElement {\n        match item {\n            BlockNode::ListItem {\n                children,\n                spread,\n                checked,\n                ..\n            } => v_flex()\n                .id((\"li\", options.ix))\n                .w_full()\n                .min_w_0()\n                .when(*spread, |this| this.child(div()))\n                .children({\n                    let mut items: Vec<Div> = Vec::with_capacity(children.len());\n\n                    for (child_ix, child) in children.iter().enumerate() {\n                        match child {\n                            BlockNode::Paragraph { .. } => {\n                                let last_not_list = child_ix > 0\n                                    && !matches!(children[child_ix - 1], BlockNode::List { .. });\n\n                                let text = child.render_block(\n                                    NodeRenderOptions {\n                                        depth: options.depth + 1,\n                                        todo: checked.is_some(),\n                                        is_last: true,\n                                        ..options\n                                    },\n                                    node_cx,\n                                    window,\n                                    cx,\n                                );\n\n                                // merge content into last item.\n                                if last_not_list {\n                                    if let Some(item_item) = items.last_mut() {\n                                        item_item.extend(vec![\n                                            div()\n                                                .flex_1()\n                                                .min_w_0()\n                                                .overflow_hidden()\n                                                .child(text)\n                                                .into_any_element(),\n                                        ]);\n                                        continue;\n                                    }\n                                }\n\n                                items.push(\n                                    h_flex()\n                                        .w_full()\n                                        .flex_1()\n                                        .min_w_0()\n                                        .relative()\n                                        .items_start()\n                                        .content_start()\n                                        .when(!options.todo && checked.is_none(), |this| {\n                                            this.child(list_item_prefix(\n                                                ix,\n                                                options.ordered,\n                                                options.depth,\n                                            ))\n                                        })\n                                        .when_some(*checked, |this, checked| {\n                                            // Todo list checkbox\n                                            this.child(\n                                                div()\n                                                    .flex()\n                                                    .mt(rems(0.4))\n                                                    .mr_1p5()\n                                                    .size(rems(0.875))\n                                                    .items_center()\n                                                    .justify_center()\n                                                    .rounded(cx.theme().radius.half())\n                                                    .border_1()\n                                                    .border_color(cx.theme().primary)\n                                                    .text_color(cx.theme().primary_foreground)\n                                                    .when(checked, |this| {\n                                                        this.bg(cx.theme().primary).child(\n                                                            Icon::new(IconName::Check)\n                                                                .size_2()\n                                                                .text_xs(),\n                                                        )\n                                                    }),\n                                            )\n                                        })\n                                        .child(\n                                            div()\n                                                .flex_1()\n                                                .min_w_0()\n                                                .overflow_hidden()\n                                                .child(text),\n                                        ),\n                                );\n                            }\n                            BlockNode::List { .. } => {\n                                items.push(div().ml(rems(1.)).child(child.render_block(\n                                    NodeRenderOptions {\n                                        depth: options.depth + 1,\n                                        todo: checked.is_some(),\n                                        is_last: true,\n                                        ..options\n                                    },\n                                    node_cx,\n                                    window,\n                                    cx,\n                                )));\n                            }\n                            _ => {}\n                        }\n                    }\n                    items\n                })\n                .into_any_element(),\n            _ => div().into_any_element(),\n        }\n    }\n\n    fn render_table(\n        item: &BlockNode,\n        options: &NodeRenderOptions,\n        node_cx: &NodeContext,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> impl IntoElement {\n        const DEFAULT_LENGTH: usize = 5;\n        const MAX_LENGTH: usize = 150;\n        let col_lens = match item {\n            BlockNode::Table(table) => {\n                let mut col_lens = vec![];\n                for row in table.children.iter() {\n                    for (ix, cell) in row.children.iter().enumerate() {\n                        if col_lens.len() <= ix {\n                            col_lens.push(DEFAULT_LENGTH);\n                        }\n\n                        let len = cell.children.text_len();\n                        if len > col_lens[ix] {\n                            col_lens[ix] = len;\n                        }\n                    }\n                }\n                col_lens\n            }\n            _ => vec![],\n        };\n\n        match item {\n            BlockNode::Table(table) => div()\n                .pb(rems(1.))\n                .w_full()\n                .child(\n                    div()\n                        .id((\"table\", options.ix))\n                        .w_full()\n                        .border_1()\n                        .border_color(cx.theme().border)\n                        .rounded(cx.theme().radius)\n                        .children({\n                            let mut rows = Vec::with_capacity(table.children.len());\n                            for (row_ix, row) in table.children.iter().enumerate() {\n                                rows.push(\n                                    div()\n                                        .id(\"row\")\n                                        .w_full()\n                                        .when(row_ix < table.children.len() - 1, |this| {\n                                            this.border_b_1()\n                                        })\n                                        .border_color(cx.theme().border)\n                                        .flex()\n                                        .flex_row()\n                                        .children({\n                                            let mut cells = Vec::with_capacity(row.children.len());\n                                            for (ix, cell) in row.children.iter().enumerate() {\n                                                let align = table.column_align(ix);\n                                                let is_last_col = ix == row.children.len() - 1;\n                                                let len = col_lens\n                                                    .get(ix)\n                                                    .copied()\n                                                    .unwrap_or(MAX_LENGTH)\n                                                    .min(MAX_LENGTH);\n\n                                                cells.push(\n                                                    div()\n                                                        .id(\"cell\")\n                                                        .flex()\n                                                        .when(\n                                                            align == ColumnumnAlign::Center,\n                                                            |this| this.justify_center(),\n                                                        )\n                                                        .when(\n                                                            align == ColumnumnAlign::Right,\n                                                            |this| this.justify_end(),\n                                                        )\n                                                        .w(Length::Definite(relative(len as f32)))\n                                                        .px_2()\n                                                        .py_1()\n                                                        .when(!is_last_col, |this| {\n                                                            this.border_r_1()\n                                                                .border_color(cx.theme().border)\n                                                        })\n                                                        .truncate()\n                                                        .child(\n                                                            cell.children\n                                                                .render(node_cx, window, cx),\n                                                        ),\n                                                )\n                                            }\n                                            cells\n                                        }),\n                                )\n                            }\n                            rows\n                        }),\n                )\n                .into_any_element(),\n            _ => div().into_any_element(),\n        }\n    }\n\n    pub(crate) fn render_block(\n        &self,\n        options: NodeRenderOptions,\n        node_cx: &NodeContext,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> AnyElement {\n        let ix = options.ix;\n        let mb = if options.in_list || options.is_last {\n            rems(0.)\n        } else {\n            node_cx.style.paragraph_gap\n        };\n\n        match self {\n            BlockNode::Root { children, .. } => div()\n                .id((\"div\", ix))\n                .children(children.into_iter().enumerate().map(move |(ix, node)| {\n                    node.render_block(NodeRenderOptions { ix, ..options }, node_cx, window, cx)\n                }))\n                .into_any_element(),\n            BlockNode::Paragraph(paragraph) => div()\n                .id((\"p\", ix))\n                .pb(mb)\n                .child(paragraph.render(node_cx, window, cx))\n                .into_any_element(),\n            BlockNode::Heading {\n                level, children, ..\n            } => {\n                let (text_size, font_weight) = match level {\n                    1 => (rems(2.), FontWeight::BOLD),\n                    2 => (rems(1.5), FontWeight::SEMIBOLD),\n                    3 => (rems(1.25), FontWeight::SEMIBOLD),\n                    4 => (rems(1.125), FontWeight::SEMIBOLD),\n                    5 => (rems(1.), FontWeight::SEMIBOLD),\n                    6 => (rems(1.), FontWeight::MEDIUM),\n                    _ => (rems(1.), FontWeight::NORMAL),\n                };\n\n                let mut text_size = text_size.to_pixels(node_cx.style.heading_base_font_size);\n                if let Some(f) = node_cx.style.heading_font_size.as_ref() {\n                    text_size = (f)(*level, node_cx.style.heading_base_font_size);\n                }\n\n                h_flex()\n                    .id(SharedString::from(format!(\"h{}-{}\", level, ix)))\n                    .pb(rems(0.3))\n                    .whitespace_normal()\n                    .text_size(text_size)\n                    .font_weight(font_weight)\n                    .child(children.render(node_cx, window, cx))\n                    .into_any_element()\n            }\n            BlockNode::Blockquote { children, .. } => div()\n                .w_full()\n                .pb(mb)\n                .child(\n                    div()\n                        .id((\"blockquote\", ix))\n                        .w_full()\n                        .text_color(cx.theme().muted_foreground)\n                        .border_l_3()\n                        .border_color(cx.theme().secondary_active)\n                        .px_4()\n                        .children({\n                            let children_len = children.len();\n                            children.into_iter().enumerate().map(move |(index, c)| {\n                                let is_last = index == children_len - 1;\n                                c.render_block(options.is_last(is_last), node_cx, window, cx)\n                            })\n                        }),\n                )\n                .into_any_element(),\n            BlockNode::List {\n                children, ordered, ..\n            } => v_flex()\n                .id((if *ordered { \"ol\" } else { \"ul\" }, ix))\n                .pb(mb)\n                .children({\n                    let mut items = Vec::with_capacity(children.len());\n                    let mut item_index = 0;\n                    for (ix, item) in children.into_iter().enumerate() {\n                        let is_item = item.is_list_item();\n\n                        items.push(Self::render_list_item(\n                            item,\n                            item_index,\n                            NodeRenderOptions {\n                                ix,\n                                ordered: *ordered,\n                                ..options\n                            },\n                            node_cx,\n                            window,\n                            cx,\n                        ));\n\n                        if is_item {\n                            item_index += 1;\n                        }\n                    }\n                    items\n                })\n                .into_any_element(),\n            BlockNode::CodeBlock(code_block) => code_block.render(&options, node_cx, window, cx),\n            BlockNode::Table { .. } => {\n                Self::render_table(self, &options, node_cx, window, cx).into_any_element()\n            }\n            BlockNode::Divider { .. } => div()\n                .pb(mb)\n                .child(div().id(\"divider\").bg(cx.theme().border).h(px(2.)))\n                .into_any_element(),\n            BlockNode::Break { .. } => div().id(\"break\").into_any_element(),\n            BlockNode::Unknown { .. } | BlockNode::Definition { .. } => div().into_any_element(),\n            _ => {\n                if cfg!(debug_assertions) {\n                    tracing::warn!(\"unknown implementation: {:?}\", self);\n                }\n\n                div().into_any_element()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/text/state.rs",
    "content": "use std::{\n    pin::Pin,\n    task::Poll,\n};\nuse futures::Stream as _;\n\nuse gpui::{\n    App, AppContext as _, Bounds, ClipboardItem, Context, FocusHandle, IntoElement, KeyBinding,\n    ListState, ParentElement as _, Pixels, Point, Render, SharedString, Styled as _, Task, Window,\n    prelude::FluentBuilder as _, px,\n};\n\nuse crate::{\n    ActiveTheme, ElementExt,\n    async_util::{Sender, Receiver, unbounded},\n    highlighter::HighlightTheme,\n    input::{self, Copy},\n    text::{\n        CodeBlockActionsFn, TextViewStyle,\n        document::ParsedDocument,\n        format,\n        node::{self, NodeContext},\n    },\n    v_flex,\n};\n\nconst CONTEXT: &'static str = \"TextView\";\npub(crate) fn init(cx: &mut App) {\n    cx.bind_keys(vec![\n        #[cfg(target_os = \"macos\")]\n        KeyBinding::new(\"cmd-c\", input::Copy, Some(CONTEXT)),\n        #[cfg(not(target_os = \"macos\"))]\n        KeyBinding::new(\"ctrl-c\", input::Copy, Some(CONTEXT)),\n    ]);\n}\n\n/// The content format of the text view.\n#[derive(Clone, Copy, PartialEq, Eq)]\npub(super) enum TextViewFormat {\n    /// Markdown view\n    Markdown,\n    /// HTML view\n    Html,\n}\n\n/// The state of a TextView.\npub struct TextViewState {\n    pub(super) focus_handle: FocusHandle,\n    pub(super) list_state: ListState,\n\n    /// The bounds of the text view\n    bounds: Bounds<Pixels>,\n\n    pub(super) selectable: bool,\n    pub(super) scrollable: bool,\n    pub(super) text_view_style: TextViewStyle,\n    pub(super) code_block_actions: Option<std::sync::Arc<CodeBlockActionsFn>>,\n\n    pub(super) is_selecting: bool,\n    /// The local (in TextView) position of the selection.\n    selection_positions: (Option<Point<Pixels>>, Option<Point<Pixels>>),\n\n    pub(super) parsed_content: ParsedContent,\n    text: SharedString,\n    parsed_error: Option<SharedString>,\n    tx: Sender<UpdateOptions>,\n    _parse_task: Task<()>,\n    _receive_task: Task<()>,\n}\n\nimpl TextViewState {\n    /// Create a Markdown TextViewState.\n    pub fn markdown(text: &str, cx: &mut Context<Self>) -> Self {\n        Self::new(TextViewFormat::Markdown, text, cx)\n    }\n\n    /// Create a HTML TextViewState.\n    pub fn html(text: &str, cx: &mut Context<Self>) -> Self {\n        Self::new(TextViewFormat::Html, text, cx)\n    }\n\n    /// Create a new TextViewState.\n    fn new(format: TextViewFormat, text: &str, cx: &mut Context<Self>) -> Self {\n        let focus_handle = cx.focus_handle();\n\n        let (tx, rx) = unbounded::<UpdateOptions>();\n        let (tx_result, rx_result) = unbounded::<Result<ParsedContent, SharedString>>();\n        let _receive_task = cx.spawn({\n            async move |weak_self, cx| {\n                while let Ok(parsed_result) = rx_result.recv().await {\n                    _ = weak_self.update(cx, |state, cx| {\n                        match parsed_result {\n                            Ok(content) => {\n                                state.parsed_content = content;\n                                state.parsed_error = None;\n                            }\n                            Err(err) => {\n                                state.parsed_error = Some(err);\n                            }\n                        }\n                        state.clear_selection();\n                        cx.notify();\n                    });\n                }\n            }\n        });\n\n        let _parse_task = cx.background_spawn(UpdateFuture::new(format, rx, tx_result, cx));\n\n        let mut this = Self {\n            focus_handle,\n            bounds: Bounds::default(),\n            selection_positions: (None, None),\n            selectable: false,\n            scrollable: false,\n            list_state: ListState::new(0, gpui::ListAlignment::Top, px(1000.)),\n            text_view_style: TextViewStyle::default(),\n            code_block_actions: None,\n            is_selecting: false,\n            parsed_content: Default::default(),\n            parsed_error: None,\n            text: text.to_string().into(),\n            tx,\n            _parse_task,\n            _receive_task,\n        };\n        this.increment_update(&text, false, cx);\n        this\n    }\n\n    /// Get the text content.\n    pub(crate) fn source(&self) -> SharedString {\n        self.parsed_content.document.source.clone()\n    }\n\n    /// Set whether the text is selectable, default false.\n    pub fn selectable(mut self, selectable: bool) -> Self {\n        self.selectable = selectable;\n        self\n    }\n\n    /// Set whether the text is selectable, default false.\n    pub fn set_selectable(&mut self, selectable: bool, cx: &mut Context<Self>) {\n        self.selectable = selectable;\n        cx.notify();\n    }\n\n    /// Set whether the text is selectable, default false.\n    pub fn scrollable(mut self, scrollable: bool) -> Self {\n        self.scrollable = scrollable;\n        self\n    }\n\n    /// Set whether the text is selectable, default false.\n    pub fn set_scrollable(&mut self, scrollable: bool, cx: &mut Context<Self>) {\n        self.scrollable = scrollable;\n        cx.notify();\n    }\n\n    /// Set the text content.\n    pub fn set_text(&mut self, text: &str, cx: &mut Context<Self>) {\n        if self.text.as_str() == text {\n            return;\n        }\n\n        self.text = text.to_string().into();\n        self.parsed_error = None;\n        self.increment_update(text, false, cx);\n    }\n\n    /// Append partial text content to the existing text.\n    pub fn push_str(&mut self, new_text: &str, cx: &mut Context<Self>) {\n        if new_text.is_empty() {\n            return;\n        }\n        self.increment_update(new_text, true, cx);\n    }\n\n    /// Return the selected text.\n    pub fn selected_text(&self) -> String {\n        self.parsed_content.document.selected_text()\n    }\n\n    fn increment_update(&mut self, text: &str, append: bool, cx: &mut Context<Self>) {\n        let update_options = UpdateOptions {\n            append,\n            content: self.parsed_content.clone(),\n            pending_text: text.to_string(),\n            highlight_theme: cx.theme().highlight_theme.clone(),\n        };\n\n        _ = self.tx.try_send(update_options);\n    }\n\n    /// Save bounds and unselect if bounds changed.\n    pub(super) fn update_bounds(&mut self, bounds: Bounds<Pixels>) {\n        if self.bounds.size != bounds.size {\n            self.clear_selection();\n        }\n        self.bounds = bounds;\n    }\n\n    pub(super) fn clear_selection(&mut self) {\n        self.selection_positions = (None, None);\n        self.is_selecting = false;\n    }\n\n    pub(super) fn start_selection(&mut self, pos: Point<Pixels>) {\n        // Store content coordinates (not affected by scrolling)\n        let scroll_offset = if self.scrollable {\n            self.list_state.scroll_px_offset_for_scrollbar()\n        } else {\n            Point::default()\n        };\n        let pos = pos - self.bounds.origin - scroll_offset;\n        self.selection_positions = (Some(pos), Some(pos));\n        self.is_selecting = true;\n    }\n\n    pub(super) fn update_selection(&mut self, pos: Point<Pixels>) {\n        let scroll_offset = if self.scrollable {\n            self.list_state.scroll_px_offset_for_scrollbar()\n        } else {\n            Point::default()\n        };\n        let pos = pos - self.bounds.origin - scroll_offset;\n        if let (Some(start), Some(_)) = self.selection_positions {\n            self.selection_positions = (Some(start), Some(pos))\n        }\n    }\n\n    pub(super) fn end_selection(&mut self) {\n        self.is_selecting = false;\n    }\n\n    pub(crate) fn has_selection(&self) -> bool {\n        if let (Some(start), Some(end)) = self.selection_positions {\n            start != end\n        } else {\n            false\n        }\n    }\n\n    /// Return the selection start/end in window coordinates.\n    pub(crate) fn selection_points(&self) -> Option<(Point<Pixels>, Point<Pixels>)> {\n        let scroll_offset = if self.scrollable {\n            self.list_state.scroll_px_offset_for_scrollbar()\n        } else {\n            Point::default()\n        };\n\n        selection_points(\n            self.selection_positions.0,\n            self.selection_positions.1,\n            self.bounds,\n            scroll_offset,\n        )\n    }\n\n    pub(super) fn on_action_copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {\n        let selected_text = self.selected_text().trim().to_string();\n        if selected_text.is_empty() {\n            return;\n        }\n\n        cx.write_to_clipboard(ClipboardItem::new_string(selected_text));\n    }\n\n    pub(crate) fn is_selectable(&self) -> bool {\n        self.selectable\n    }\n}\n\nimpl Render for TextViewState {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let state = cx.entity();\n        let document = self.parsed_content.document.clone();\n        let mut node_cx = self.parsed_content.node_cx.clone();\n\n        node_cx.code_block_actions = self.code_block_actions.clone();\n        node_cx.style = self.text_view_style.clone();\n\n        v_flex()\n            .size_full()\n            .map(|this| match &mut self.parsed_error {\n                None => this.child(document.render_root(\n                    if self.scrollable {\n                        Some(self.list_state.clone())\n                    } else {\n                        None\n                    },\n                    &node_cx,\n                    window,\n                    cx,\n                )),\n                Some(err) => this.child(\n                    v_flex()\n                        .gap_1()\n                        .child(\"Failed to parse content\")\n                        .child(err.to_string()),\n                ),\n            })\n            .on_prepaint(move |bounds, _, cx| {\n                state.update(cx, |state, _| {\n                    state.update_bounds(bounds);\n                })\n            })\n    }\n}\n\n#[derive(Clone, PartialEq, Default)]\npub(crate) struct ParsedContent {\n    pub(crate) document: ParsedDocument,\n    pub(crate) node_cx: node::NodeContext,\n}\n\nstruct UpdateFuture {\n    format: TextViewFormat,\n    options: UpdateOptions,\n    pending_text: String,\n    rx: Pin<Box<Receiver<UpdateOptions>>>,\n    tx_result: Sender<Result<ParsedContent, SharedString>>,\n}\n\nimpl UpdateFuture {\n    fn new(\n        format: TextViewFormat,\n        rx: Receiver<UpdateOptions>,\n        tx_result: Sender<Result<ParsedContent, SharedString>>,\n        cx: &App,\n    ) -> Self {\n        Self {\n            format,\n            pending_text: String::new(),\n            options: UpdateOptions {\n                append: false,\n                pending_text: String::new(),\n                content: Default::default(),\n                highlight_theme: cx.theme().highlight_theme.clone(),\n            },\n            rx: Box::pin(rx),\n            tx_result,\n        }\n    }\n}\n\nimpl Future for UpdateFuture {\n    type Output = ();\n\n    fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {\n        loop {\n            match self.rx.as_mut().poll_next(cx) {\n                Poll::Ready(Some(options)) => {\n                    if options.append {\n                        self.pending_text.push_str(options.pending_text.as_str());\n                    } else {\n                        self.pending_text = options.pending_text.clone();\n                    }\n                    self.options = options;\n\n                    // Process immediately without debounce\n                    let pending_text = std::mem::take(&mut self.pending_text);\n                    let res = parse_content(\n                        self.format,\n                        &UpdateOptions {\n                            pending_text,\n                            ..self.options.clone()\n                        },\n                    );\n                    _ = self.tx_result.try_send(res);\n                    continue;\n                }\n                Poll::Ready(None) => return Poll::Ready(()),\n                Poll::Pending => return Poll::Pending,\n            }\n        }\n    }\n}\n\n#[derive(Clone)]\nstruct UpdateOptions {\n    content: ParsedContent,\n    pending_text: String,\n    append: bool,\n    highlight_theme: std::sync::Arc<HighlightTheme>,\n}\n\nfn parse_content(format: TextViewFormat, options: &UpdateOptions) -> Result<ParsedContent, SharedString> {\n    let mut node_cx = NodeContext {\n        ..NodeContext::default()\n    };\n\n    let mut content = options.content.clone();\n    let mut source = String::new();\n    if options.append\n        && let Some(last_block) = content.document.blocks.pop()\n        && let Some(span) = last_block.span()\n    {\n        node_cx.offset = span.start;\n        let last_source = &content.document.source[span.start..];\n        source.push_str(last_source);\n        source.push_str(&options.pending_text);\n    } else {\n        source = options.pending_text.to_string();\n    }\n\n    let new_document = match format {\n        TextViewFormat::Markdown => {\n            format::markdown::parse(&source, &mut node_cx, &options.highlight_theme)\n        }\n        TextViewFormat::Html => format::html::parse(&source, &mut node_cx),\n    }?;\n\n    if options.append {\n        content.document.source =\n            format!(\"{}{}\", content.document.source, options.pending_text).into();\n        content.document.blocks.extend(new_document.blocks);\n    } else {\n        content.document = new_document;\n    }\n\n    Ok(content)\n}\n\nfn selection_points(\n    start: Option<Point<Pixels>>,\n    end: Option<Point<Pixels>>,\n    bounds: Bounds<Pixels>,\n    scroll_offset: Point<Pixels>,\n) -> Option<(Point<Pixels>, Point<Pixels>)> {\n    if let (Some(start), Some(end)) = (start, end) {\n        // Convert content coordinates to window coordinates\n        let start = start + scroll_offset + bounds.origin;\n        let end = end + scroll_offset + bounds.origin;\n        return Some((start, end));\n    }\n\n    None\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use gpui::point;\n\n    #[test]\n    fn test_text_view_state_selection_points() {\n        assert_eq!(\n            selection_points(None, None, Default::default(), Point::default()),\n            None\n        );\n        assert_eq!(\n            selection_points(\n                None,\n                Some(point(px(10.), px(20.))),\n                Default::default(),\n                Point::default()\n            ),\n            None\n        );\n        assert_eq!(\n            selection_points(\n                Some(point(px(10.), px(20.))),\n                None,\n                Default::default(),\n                Point::default()\n            ),\n            None\n        );\n\n        // 10,10 start\n        //   |------|\n        //   |      |\n        //   |------|\n        //         50,50\n        assert_eq!(\n            selection_points(\n                Some(point(px(10.), px(10.))),\n                Some(point(px(50.), px(50.))),\n                Default::default(),\n                Point::default()\n            ),\n            Some((point(px(10.), px(10.)), point(px(50.), px(50.))))\n        );\n\n        // 10,10\n        //   |------|\n        //   |      |\n        //   |------|\n        //         50,50 start\n        assert_eq!(\n            selection_points(\n                Some(point(px(50.), px(50.))),\n                Some(point(px(10.), px(10.))),\n                Default::default(),\n                Point::default()\n            ),\n            Some((point(px(50.), px(50.)), point(px(10.), px(10.))))\n        );\n\n        //        50,10 start\n        //   |------|\n        //   |      |\n        //   |------|\n        // 10,50\n        assert_eq!(\n            selection_points(\n                Some(point(px(50.), px(10.))),\n                Some(point(px(10.), px(50.))),\n                Default::default(),\n                Point::default()\n            ),\n            Some((point(px(50.), px(10.)), point(px(10.), px(50.))))\n        );\n\n        //        50,10\n        //   |------|\n        //   |      |\n        //   |------|\n        // 10,50 start\n        assert_eq!(\n            selection_points(\n                Some(point(px(10.), px(50.))),\n                Some(point(px(50.), px(10.))),\n                Default::default(),\n                Point::default()\n            ),\n            Some((point(px(10.), px(50.)), point(px(50.), px(10.))))\n        );\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/text/style.rs",
    "content": "use std::sync::Arc;\n\nuse gpui::{Pixels, Rems, StyleRefinement, px, rems};\n\nuse crate::highlighter::HighlightTheme;\n\n/// TextViewStyle used to customize the style for [`TextView`].\n#[derive(Clone)]\npub struct TextViewStyle {\n    /// Gap of each paragraphs, default is 1 rem.\n    pub paragraph_gap: Rems,\n    /// Base font size for headings, default is 14px.\n    pub heading_base_font_size: Pixels,\n    /// Function to calculate heading font size based on heading level (1-6).\n    ///\n    /// The first parameter is the heading level (1-6), the second parameter is the base font size.\n    /// The second parameter is the base font size.\n    pub heading_font_size: Option<Arc<dyn Fn(u8, Pixels) -> Pixels + Send + Sync + 'static>>,\n    /// Highlight theme for code blocks. Default: [`HighlightTheme::default_light()`]\n    pub highlight_theme: Arc<HighlightTheme>,\n    /// The style refinement for code blocks.\n    pub code_block: StyleRefinement,\n    pub is_dark: bool,\n}\n\nimpl PartialEq for TextViewStyle {\n    fn eq(&self, other: &Self) -> bool {\n        self.paragraph_gap == other.paragraph_gap\n            && self.heading_base_font_size == other.heading_base_font_size\n            && self.highlight_theme == other.highlight_theme\n    }\n}\n\nimpl Default for TextViewStyle {\n    fn default() -> Self {\n        Self {\n            paragraph_gap: rems(1.),\n            heading_base_font_size: px(14.),\n            heading_font_size: None,\n            highlight_theme: HighlightTheme::default_light().clone(),\n            code_block: StyleRefinement::default(),\n            is_dark: false,\n        }\n    }\n}\n\nimpl TextViewStyle {\n    /// Set paragraph gap, default is 1 rem.\n    pub fn paragraph_gap(mut self, gap: Rems) -> Self {\n        self.paragraph_gap = gap;\n        self\n    }\n\n    pub fn heading_font_size<F>(mut self, f: F) -> Self\n    where\n        F: Fn(u8, Pixels) -> Pixels + Send + Sync + 'static,\n    {\n        self.heading_font_size = Some(Arc::new(f));\n        self\n    }\n\n    /// Set style for code blocks.\n    pub fn code_block(mut self, style: StyleRefinement) -> Self {\n        self.code_block = style;\n        self\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/text/text_view.rs",
    "content": "use std::sync::Arc;\n\nuse gpui::prelude::FluentBuilder as _;\nuse gpui::{\n    AnyElement, App, Bounds, Element, ElementId, Entity, GlobalElementId, Hitbox, HitboxBehavior,\n    InspectorElementId, InteractiveElement, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent,\n    MouseUpEvent, ParentElement, Pixels, SharedString, StyleRefinement, Styled, Window, div,\n};\n\nuse crate::StyledExt;\nuse crate::scroll::ScrollableElement;\nuse crate::text::TextViewFormat;\nuse crate::text::node::CodeBlock;\nuse crate::text::state::TextViewState;\nuse crate::{global_state::GlobalState, text::TextViewStyle};\n\n/// Type for code block actions generator function.\npub(crate) type CodeBlockActionsFn =\n    dyn Fn(&CodeBlock, &mut Window, &mut App) -> AnyElement + Send + Sync;\n\n/// A text view that can render Markdown or HTML.\n///\n/// ## Goals\n///\n/// - Provide a rich text rendering component for such as Markdown or HTML,\n/// used to display rich text in GPUI application (e.g., Help messages, Release notes)\n/// - Support Markdown GFM and HTML (Simple HTML like Safari Reader Mode) for showing most common used markups.\n/// - Support Heading, Paragraph, Bold, Italic, StrikeThrough, Code, Link, Image, Blockquote, List, Table, HorizontalRule, CodeBlock ...\n///\n/// ## Not Goals\n///\n/// - Customization of the complex style (some simple styles will be supported)\n/// - As a Markdown editor or viewer (If you want to like this, you must fork your version).\n/// - As a HTML viewer, we not support CSS, we only support basic HTML tags for used to as a content reader.\n///\n/// See also [`MarkdownElement`], [`HtmlElement`]\n#[derive(Clone)]\npub struct TextView {\n    id: ElementId,\n    format: Option<TextViewFormat>,\n    text: Option<SharedString>,\n    pub(crate) state: Option<Entity<TextViewState>>,\n    text_view_style: TextViewStyle,\n    style: StyleRefinement,\n    selectable: bool,\n    scrollable: bool,\n    code_block_actions: Option<Arc<CodeBlockActionsFn>>,\n}\n\nimpl Styled for TextView {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl TextView {\n    /// Create new TextView with managed state.\n    pub fn new(state: &Entity<TextViewState>) -> Self {\n        Self {\n            id: ElementId::Name(state.entity_id().to_string().into()),\n            state: Some(state.clone()),\n            format: None,\n            text: None,\n            text_view_style: TextViewStyle::default(),\n            style: StyleRefinement::default(),\n            selectable: false,\n            scrollable: false,\n            code_block_actions: None,\n        }\n    }\n\n    /// Create a new markdown text view.\n    pub fn markdown(id: impl Into<ElementId>, markdown: impl Into<SharedString>) -> Self {\n        Self {\n            id: id.into(),\n            format: Some(TextViewFormat::Markdown),\n            text: Some(markdown.into()),\n            text_view_style: TextViewStyle::default(),\n            style: StyleRefinement::default(),\n            state: None,\n            selectable: false,\n            scrollable: false,\n            code_block_actions: None,\n        }\n    }\n\n    /// Create a new html text view.\n    pub fn html(id: impl Into<ElementId>, html: impl Into<SharedString>) -> Self {\n        Self {\n            id: id.into(),\n            format: Some(TextViewFormat::Html),\n            text: Some(html.into()),\n            text_view_style: TextViewStyle::default(),\n            style: StyleRefinement::default(),\n            state: None,\n            selectable: false,\n            scrollable: false,\n            code_block_actions: None,\n        }\n    }\n\n    /// Set [`TextViewStyle`].\n    pub fn style(mut self, style: TextViewStyle) -> Self {\n        self.text_view_style = style;\n        self\n    }\n\n    /// Set the text view to be selectable, default is false.\n    pub fn selectable(mut self, selectable: bool) -> Self {\n        self.selectable = selectable;\n        self\n    }\n\n    /// Set the text view to be scrollable, default is false.\n    ///\n    /// ## If true for `scrollable`\n    ///\n    /// The `scrollable` mode used for large content,\n    /// will show scrollbar, but requires the parent to have a fixed height,\n    /// and use [`gpui::list`] to render the content in a virtualized way.\n    ///\n    /// ## If false to fit content\n    ///\n    /// The TextView will expand to fit all content, no scrollbar.\n    /// This mode is suitable for small content, such as a few lines of text, a label, etc.\n    pub fn scrollable(mut self, scrollable: bool) -> Self {\n        self.scrollable = scrollable;\n        self\n    }\n\n    /// Set custom block actions for code blocks.\n    ///\n    /// The closure receives the [`CodeBlock`],\n    /// and returns an element to display.\n    pub fn code_block_actions<F, E>(mut self, f: F) -> Self\n    where\n        F: Fn(&CodeBlock, &mut Window, &mut App) -> E + Send + Sync + 'static,\n        E: IntoElement,\n    {\n        self.code_block_actions = Some(Arc::new(move |code_block, window, cx| {\n            f(&code_block, window, cx).into_any_element()\n        }));\n        self\n    }\n}\n\nimpl IntoElement for TextView {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\npub struct TextViewLayoutState {\n    state: Entity<TextViewState>,\n    element: AnyElement,\n}\n\nimpl Element for TextView {\n    type RequestLayoutState = TextViewLayoutState;\n    type PrepaintState = Hitbox;\n\n    fn id(&self) -> Option<ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        _: Option<&GlobalElementId>,\n        _: Option<&InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (LayoutId, Self::RequestLayoutState) {\n        let state = if let Some(state) = self.state.clone() {\n            state\n        } else {\n            let default_format = self.format.unwrap_or(TextViewFormat::Markdown);\n            let default_text = self.text.clone().unwrap_or_default();\n\n            let state = window.use_keyed_state(\n                SharedString::from(format!(\"{}/state\", self.id)),\n                cx,\n                move |_, cx| {\n                    if default_format == TextViewFormat::Markdown {\n                        TextViewState::markdown(default_text.as_str(), cx)\n                    } else {\n                        TextViewState::html(default_text.as_str(), cx)\n                    }\n                },\n            );\n            self.state = Some(state.clone());\n            state\n        };\n\n        state.update(cx, |state, cx| {\n            state.code_block_actions = self.code_block_actions.clone();\n            state.selectable = self.selectable;\n            state.scrollable = self.scrollable;\n            state.text_view_style = self.text_view_style.clone();\n\n            if let Some(text) = self.text.clone() {\n                state.set_text(text.as_str(), cx);\n            }\n        });\n\n        let focus_handle = state.read(cx).focus_handle.clone();\n        let list_state = state.read(cx).list_state.clone();\n\n        let mut el = div()\n            .key_context(\"TextView\")\n            .track_focus(&focus_handle)\n            .when(self.scrollable, |this| {\n                this.size_full().vertical_scrollbar(&list_state)\n            })\n            .relative()\n            .on_action(window.listener_for(&state, TextViewState::on_action_copy))\n            .child(state.clone())\n            .refine_style(&self.style)\n            .into_any_element();\n        let layout_id = el.request_layout(window, cx);\n        (layout_id, TextViewLayoutState { state, element: el })\n    }\n\n    fn prepaint(\n        &mut self,\n        _: Option<&GlobalElementId>,\n        _: Option<&InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        request_layout: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::PrepaintState {\n        request_layout.element.prepaint(window, cx);\n        window.insert_hitbox(bounds, HitboxBehavior::Normal)\n    }\n\n    fn paint(\n        &mut self,\n        _: Option<&GlobalElementId>,\n        _: Option<&InspectorElementId>,\n        _bounds: Bounds<Pixels>,\n        request_layout: &mut Self::RequestLayoutState,\n        hitbox: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        let state = &request_layout.state;\n        GlobalState::global_mut(cx)\n            .text_view_state_stack\n            .push(state.clone());\n        request_layout.element.paint(window, cx);\n        GlobalState::global_mut(cx).text_view_state_stack.pop();\n\n        if self.selectable {\n            let is_selecting = state.read(cx).is_selecting;\n            let has_selection = state.read(cx).has_selection();\n            let parent_view_id = window.current_view();\n\n            window.on_mouse_event({\n                let state = state.clone();\n                let hitbox = hitbox.clone();\n                move |event: &MouseDownEvent, phase, window, cx| {\n                    if !phase.bubble() || !hitbox.is_hovered(window) {\n                        return;\n                    }\n\n                    state.update(cx, |state, _| {\n                        state.start_selection(event.position);\n                    });\n                    cx.notify(parent_view_id);\n                }\n            });\n\n            if is_selecting {\n                // move to update end position.\n                window.on_mouse_event({\n                    let state = state.clone();\n                    move |event: &MouseMoveEvent, phase, _, cx| {\n                        if !phase.bubble() {\n                            return;\n                        }\n\n                        state.update(cx, |state, _| {\n                            state.update_selection(event.position);\n                        });\n                        cx.notify(parent_view_id);\n                    }\n                });\n\n                // up to end selection\n                window.on_mouse_event({\n                    let state = state.clone();\n                    move |_: &MouseUpEvent, phase, _, cx| {\n                        if !phase.bubble() {\n                            return;\n                        }\n\n                        state.update(cx, |state, _| {\n                            state.end_selection();\n                        });\n                        cx.notify(parent_view_id);\n                    }\n                });\n            }\n\n            if has_selection {\n                // down outside to clear selection\n                window.on_mouse_event({\n                    let state = state.clone();\n                    let hitbox = hitbox.clone();\n                    move |_: &MouseDownEvent, _, window, cx| {\n                        if hitbox.is_hovered(window) {\n                            return;\n                        }\n\n                        state.update(cx, |state, _| {\n                            state.clear_selection();\n                        });\n                        cx.notify(parent_view_id);\n                    }\n                });\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::TextView;\n    use crate::text::TextViewState;\n    use gpui::{\n        AppContext as _, Context, Entity, IntoElement, Modifiers, MouseButton, ParentElement as _,\n        Render, Styled as _, TestAppContext, VisualTestContext, Window, div, point, px,\n    };\n\n    struct TextViewTestRoot {\n        text_view: Entity<TextViewState>,\n    }\n\n    impl TextViewTestRoot {\n        fn new(text: &str, cx: &mut Context<Self>) -> Self {\n            let text = text.to_string();\n            let text_view = cx.new(|cx| TextViewState::markdown(&text, cx));\n            Self { text_view }\n        }\n    }\n\n    impl Render for TextViewTestRoot {\n        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {\n            div()\n                .w(px(160.))\n                .child(\n                    div()\n                        .h(px(24.))\n                        .overflow_hidden()\n                        .child(TextView::new(&self.text_view).selectable(true)),\n                )\n                .child(div().h(px(40.)).child(\"footer\"))\n        }\n    }\n\n    #[gpui::test]\n    fn clipped_markdown_link_does_not_open(cx: &mut TestAppContext) {\n        cx.update(crate::init);\n        let (_, cx) = cx.add_window_view(|_, cx| {\n            TextViewTestRoot::new(\"visible\\n\\n[hidden](https://example.com)\", cx)\n        });\n        let cx: &mut VisualTestContext = cx;\n\n        cx.simulate_click(point(px(10.), px(34.)), Modifiers::default());\n\n        assert_eq!(cx.opened_url(), None);\n    }\n\n    #[gpui::test]\n    fn clipped_markdown_cannot_start_selection(cx: &mut TestAppContext) {\n        cx.update(crate::init);\n        let (view, cx) = cx\n            .add_window_view(|_, cx| TextViewTestRoot::new(\"visible\\n\\nhidden selection text\", cx));\n        let cx: &mut VisualTestContext = cx;\n\n        cx.simulate_mouse_down(\n            point(px(10.), px(34.)),\n            MouseButton::Left,\n            Modifiers::default(),\n        );\n        cx.simulate_mouse_move(\n            point(px(90.), px(34.)),\n            Some(MouseButton::Left),\n            Modifiers::default(),\n        );\n        cx.simulate_mouse_up(\n            point(px(90.), px(34.)),\n            MouseButton::Left,\n            Modifiers::default(),\n        );\n\n        let selected_text = view.read_with(cx, |root, cx| root.text_view.read(cx).selected_text());\n        assert!(\n            selected_text.is_empty(),\n            \"unexpected selection: {selected_text:?}\"\n        );\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/text/utils.rs",
    "content": "const NUMBERED_PREFIXES_1: &str = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\nconst NUMBERED_PREFIXES_2: &str = \"abcdefghijklmnopqrstuvwxyz\";\n\nconst BULLETS: [&str; 5] = [\"•\", \"◦\", \"▪\", \"‣\", \"⁃\"];\n\n/// Returns the prefix for a list item.\npub(super) fn list_item_prefix(ix: usize, ordered: bool, depth: usize) -> String {\n    if ordered {\n        if depth == 0 {\n            return format!(\"{}. \", ix + 1);\n        }\n\n        if depth == 1 {\n            return format!(\n                \"{}. \",\n                NUMBERED_PREFIXES_1\n                    .chars()\n                    .nth(ix % NUMBERED_PREFIXES_1.len())\n                    .unwrap()\n            );\n        } else {\n            return format!(\n                \"{}. \",\n                NUMBERED_PREFIXES_2\n                    .chars()\n                    .nth(ix % NUMBERED_PREFIXES_2.len())\n                    .unwrap()\n            );\n        }\n    } else {\n        let depth = depth.min(BULLETS.len() - 1);\n        let bullet = BULLETS[depth];\n        return format!(\"{} \", bullet);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::text::utils::list_item_prefix;\n\n    #[test]\n    fn test_list_item_prefix() {\n        assert_eq!(list_item_prefix(0, true, 0), \"1. \");\n        assert_eq!(list_item_prefix(1, true, 0), \"2. \");\n        assert_eq!(list_item_prefix(2, true, 0), \"3. \");\n        assert_eq!(list_item_prefix(10, true, 0), \"11. \");\n        assert_eq!(list_item_prefix(0, true, 1), \"A. \");\n        assert_eq!(list_item_prefix(1, true, 1), \"B. \");\n        assert_eq!(list_item_prefix(2, true, 1), \"C. \");\n        assert_eq!(list_item_prefix(0, true, 2), \"a. \");\n        assert_eq!(list_item_prefix(1, true, 2), \"b. \");\n        assert_eq!(list_item_prefix(6, true, 2), \"g. \");\n        assert_eq!(list_item_prefix(0, true, 1), \"A. \");\n        assert_eq!(list_item_prefix(0, true, 2), \"a. \");\n        assert_eq!(list_item_prefix(0, false, 0), \"• \");\n        assert_eq!(list_item_prefix(0, false, 1), \"◦ \");\n        assert_eq!(list_item_prefix(0, false, 2), \"▪ \");\n        assert_eq!(list_item_prefix(0, false, 3), \"‣ \");\n        assert_eq!(list_item_prefix(0, false, 4), \"⁃ \");\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/theme/color.rs",
    "content": "use std::{collections::HashMap, fmt::Display};\n\nuse gpui::{Hsla, SharedString, hsla};\nuse serde::{Deserialize, Deserializer, de::Error as _};\n\nuse anyhow::{Error, Result, anyhow};\n\n/// Create a [`gpui::Hsla`] color.\n///\n/// - h: 0..360.0\n/// - s: 0.0..100.0\n/// - l: 0.0..100.0\n#[inline]\npub fn hsl(h: f32, s: f32, l: f32) -> Hsla {\n    hsla(h / 360., s / 100.0, l / 100.0, 1.0)\n}\n\npub trait Colorize: Sized {\n    /// Returns a new color with the given opacity.\n    ///\n    /// The opacity is a value between 0.0 and 1.0, where 0.0 is fully transparent and 1.0 is fully opaque.\n    fn opacity(&self, opacity: f32) -> Self;\n    /// Returns a new color with each channel divided by the given divisor.\n    ///\n    /// The divisor in range of 0.0 .. 1.0\n    fn divide(&self, divisor: f32) -> Self;\n    /// Return inverted color\n    fn invert(&self) -> Self;\n    /// Return inverted lightness\n    fn invert_l(&self) -> Self;\n    /// Return a new color with the lightness increased by the given factor.\n    ///\n    /// factor range: 0.0 .. 1.0\n    fn lighten(&self, amount: f32) -> Self;\n    /// Return a new color with the darkness increased by the given factor.\n    ///\n    /// factor range: 0.0 .. 1.0\n    fn darken(&self, amount: f32) -> Self;\n    /// Return a new color with the same lightness and alpha but different hue and saturation.\n    fn apply(&self, base_color: Self) -> Self;\n\n    /// Mix two colors together, the `factor` is a value between 0.0 and 1.0 for first color.\n    fn mix(&self, other: Self, factor: f32) -> Self;\n    /// Mix two colors together in Oklab color space, the `factor` is a value between 0.0 and 1.0 for first color.\n    ///\n    /// This is similar to CSS `color-mix(in oklab, color1 factor%, color2)`.\n    fn mix_oklab(&self, other: Self, factor: f32) -> Self;\n    /// Change the `Hue` of the color by the given in range: 0.0 .. 1.0\n    fn hue(&self, hue: f32) -> Self;\n    /// Change the `Saturation` of the color by the given value in range: 0.0 .. 1.0\n    fn saturation(&self, saturation: f32) -> Self;\n    /// Change the `Lightness` of the color by the given value in range: 0.0 .. 1.0\n    fn lightness(&self, lightness: f32) -> Self;\n\n    /// Convert the color to a hex string. For example, \"#F8FAFC\".\n    fn to_hex(&self) -> String;\n    /// Parse a hex string to a color.\n    fn parse_hex(hex: &str) -> Result<Self>;\n}\n\n/// Helper functions for Oklab color space conversions\nmod oklab {\n    use gpui::Rgba;\n\n    /// Convert sRGB component to linear RGB\n    #[inline]\n    fn to_linear(c: f32) -> f32 {\n        if c <= 0.04045 {\n            c / 12.92\n        } else {\n            ((c + 0.055) / 1.055).powf(2.4)\n        }\n    }\n\n    /// Convert linear RGB component to sRGB\n    #[inline]\n    fn from_linear(c: f32) -> f32 {\n        if c <= 0.0031308 {\n            c * 12.92\n        } else {\n            1.055 * c.powf(1.0 / 2.4) - 0.055\n        }\n    }\n\n    /// Convert RGB to Oklab color space\n    #[allow(non_snake_case)]\n    pub fn rgb_to_oklab(rgb: Rgba) -> (f32, f32, f32) {\n        // sRGB to linear RGB\n        let lr = to_linear(rgb.r);\n        let lg = to_linear(rgb.g);\n        let lb = to_linear(rgb.b);\n\n        // Linear RGB to LMS\n        let l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb;\n        let m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb;\n        let s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb;\n\n        // LMS to Oklab (using cube root)\n        let l_ = l.cbrt();\n        let m_ = m.cbrt();\n        let s_ = s.cbrt();\n\n        let L = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_;\n        let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_;\n        let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_;\n\n        (L, a, b)\n    }\n\n    /// Convert Oklab to RGB color space\n    #[allow(non_snake_case)]\n    pub fn oklab_to_rgb(L: f32, a: f32, b: f32) -> Rgba {\n        // Oklab to LMS\n        let l_ = L + 0.3963377774 * a + 0.2158037573 * b;\n        let m_ = L - 0.1055613458 * a - 0.0638541728 * b;\n        let s_ = L - 0.0894841775 * a - 1.2914855480 * b;\n\n        let l = l_ * l_ * l_;\n        let m = m_ * m_ * m_;\n        let s = s_ * s_ * s_;\n\n        // LMS to Linear RGB\n        let lr = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;\n        let lg = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;\n        let lb = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;\n\n        // Linear RGB to sRGB\n        Rgba {\n            r: from_linear(lr).clamp(0.0, 1.0),\n            g: from_linear(lg).clamp(0.0, 1.0),\n            b: from_linear(lb).clamp(0.0, 1.0),\n            a: 1.0,\n        }\n    }\n}\n\nimpl Colorize for Hsla {\n    fn opacity(&self, factor: f32) -> Self {\n        Self {\n            a: self.a * factor.clamp(0.0, 1.0),\n            ..*self\n        }\n    }\n\n    fn divide(&self, divisor: f32) -> Self {\n        Self {\n            a: divisor,\n            ..*self\n        }\n    }\n\n    fn invert(&self) -> Self {\n        Self {\n            h: 1.0 - self.h,\n            s: 1.0 - self.s,\n            l: 1.0 - self.l,\n            a: self.a,\n        }\n    }\n\n    fn invert_l(&self) -> Self {\n        Self {\n            l: 1.0 - self.l,\n            ..*self\n        }\n    }\n\n    fn lighten(&self, factor: f32) -> Self {\n        let l = self.l * (1.0 + factor.clamp(0.0, 1.0));\n\n        Hsla { l, ..*self }\n    }\n\n    fn darken(&self, factor: f32) -> Self {\n        let l = self.l * (1.0 - factor.clamp(0.0, 1.0));\n\n        Self { l, ..*self }\n    }\n\n    fn apply(&self, new_color: Self) -> Self {\n        Hsla {\n            h: new_color.h,\n            s: new_color.s,\n            l: self.l,\n            a: self.a,\n        }\n    }\n\n    /// Reference:\n    /// https://github.com/bevyengine/bevy/blob/85eceb022da0326b47ac2b0d9202c9c9f01835bb/crates/bevy_color/src/hsla.rs#L112\n    fn mix(&self, other: Self, factor: f32) -> Self {\n        let factor = factor.clamp(0.0, 1.0);\n        let inv = 1.0 - factor;\n\n        #[inline]\n        fn lerp_hue(a: f32, b: f32, t: f32) -> f32 {\n            let diff = (b - a + 180.0).rem_euclid(360.) - 180.;\n            (a + diff * t).rem_euclid(360.0)\n        }\n\n        Hsla {\n            h: lerp_hue(self.h * 360., other.h * 360., factor) / 360.,\n            s: self.s * factor + other.s * inv,\n            l: self.l * factor + other.l * inv,\n            a: self.a * factor + other.a * inv,\n        }\n    }\n\n    #[allow(non_snake_case)]\n    fn mix_oklab(&self, other: Self, factor: f32) -> Self {\n        let factor = factor.clamp(0.0, 1.0);\n        let inv = 1.0 - factor;\n\n        // Interpolate alpha first\n        let result_alpha = self.a * factor + other.a * inv;\n\n        // Handle the case where result alpha is zero\n        if result_alpha == 0.0 {\n            return Self {\n                h: 0.0,\n                s: 0.0,\n                l: 0.0,\n                a: 0.0,\n            };\n        }\n\n        // Convert both colors to RGB\n        let rgb1 = self.to_rgb();\n        let rgb2 = other.to_rgb();\n\n        // Convert to Oklab color space\n        let (l1, a1, b1) = oklab::rgb_to_oklab(rgb1);\n        let (l2, a2, b2) = oklab::rgb_to_oklab(rgb2);\n\n        // Premultiply alpha in Oklab space (using alpha-premultiplied interpolation)\n        // This matches CSS color-mix behavior\n        let alpha1 = self.a;\n        let alpha2 = other.a;\n\n        // Premultiply\n        let l1_pm = l1 * alpha1;\n        let a1_pm = a1 * alpha1;\n        let b1_pm = b1 * alpha1;\n\n        let l2_pm = l2 * alpha2;\n        let a2_pm = a2 * alpha2;\n        let b2_pm = b2 * alpha2;\n\n        // Interpolate premultiplied values\n        let L_pm = l1_pm * factor + l2_pm * inv;\n        let a_pm = a1_pm * factor + a2_pm * inv;\n        let b_pm = b1_pm * factor + b2_pm * inv;\n\n        // Unpremultiply\n        let L = L_pm / result_alpha;\n        let a = a_pm / result_alpha;\n        let b = b_pm / result_alpha;\n\n        // Convert back to RGB\n        let mut rgb = oklab::oklab_to_rgb(L, a, b);\n        rgb.a = result_alpha;\n\n        // Convert RGB to HSLA\n        rgb.into()\n    }\n\n    fn to_hex(&self) -> String {\n        let rgb = self.to_rgb();\n\n        if rgb.a < 1. {\n            return format!(\n                \"#{:02X}{:02X}{:02X}{:02X}\",\n                ((rgb.r * 255.) as u32),\n                ((rgb.g * 255.) as u32),\n                ((rgb.b * 255.) as u32),\n                ((self.a * 255.) as u32)\n            );\n        }\n\n        format!(\n            \"#{:02X}{:02X}{:02X}\",\n            ((rgb.r * 255.) as u32),\n            ((rgb.g * 255.) as u32),\n            ((rgb.b * 255.) as u32)\n        )\n    }\n\n    fn parse_hex(hex: &str) -> Result<Self> {\n        let hex = hex.trim_start_matches('#');\n        let len = hex.len();\n        if len != 6 && len != 8 {\n            return Err(anyhow::anyhow!(\"invalid hex color\"));\n        }\n\n        let r = u8::from_str_radix(&hex[0..2], 16)? as f32 / 255.;\n        let g = u8::from_str_radix(&hex[2..4], 16)? as f32 / 255.;\n        let b = u8::from_str_radix(&hex[4..6], 16)? as f32 / 255.;\n        let a = if len == 8 {\n            u8::from_str_radix(&hex[6..8], 16)? as f32 / 255.\n        } else {\n            1.\n        };\n\n        let v = gpui::Rgba { r, g, b, a };\n        let color: Hsla = v.into();\n        Ok(color)\n    }\n\n    fn hue(&self, hue: f32) -> Self {\n        let mut color = *self;\n        color.h = hue.clamp(0., 1.);\n        color\n    }\n\n    fn saturation(&self, saturation: f32) -> Self {\n        let mut color = *self;\n        color.s = saturation.clamp(0., 1.);\n        color\n    }\n\n    fn lightness(&self, lightness: f32) -> Self {\n        let mut color = *self;\n        color.l = lightness.clamp(0., 1.);\n        color\n    }\n}\n\npub(crate) static DEFAULT_COLORS: once_cell::sync::Lazy<ShadcnColors> =\n    once_cell::sync::Lazy::new(|| {\n        serde_json::from_str(include_str!(\"./default-colors.json\"))\n            .expect(\"failed to parse default-colors.json\")\n    });\n\ntype ColorScales = HashMap<usize, ShadcnColor>;\n\nmod color_scales {\n    use std::collections::HashMap;\n\n    use super::{ColorScales, ShadcnColor};\n\n    use serde::de::{Deserialize, Deserializer};\n\n    pub fn deserialize<'de, D>(deserializer: D) -> Result<ColorScales, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        let mut map = HashMap::new();\n        for color in Vec::<ShadcnColor>::deserialize(deserializer)? {\n            map.insert(color.scale, color);\n        }\n        Ok(map)\n    }\n}\n\n/// Enum representing the available color names.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum ColorName {\n    White,\n    Black,\n    Neutral,\n    Gray,\n    Red,\n    Orange,\n    Amber,\n    Yellow,\n    Lime,\n    Green,\n    Emerald,\n    Teal,\n    Cyan,\n    Sky,\n    Blue,\n    Indigo,\n    Violet,\n    Purple,\n    Fuchsia,\n    Pink,\n    Rose,\n}\n\nimpl Display for ColorName {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{:?}\", self)\n    }\n}\n\n// Strict color name parser.\nimpl TryFrom<&str> for ColorName {\n    type Error = anyhow::Error;\n    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {\n        match value.to_lowercase().as_str() {\n            \"white\" => Ok(ColorName::White),\n            \"black\" => Ok(ColorName::Black),\n            \"neutral\" => Ok(ColorName::Neutral),\n            \"gray\" => Ok(ColorName::Gray),\n            \"red\" => Ok(ColorName::Red),\n            \"orange\" => Ok(ColorName::Orange),\n            \"amber\" => Ok(ColorName::Amber),\n            \"yellow\" => Ok(ColorName::Yellow),\n            \"lime\" => Ok(ColorName::Lime),\n            \"green\" => Ok(ColorName::Green),\n            \"emerald\" => Ok(ColorName::Emerald),\n            \"teal\" => Ok(ColorName::Teal),\n            \"cyan\" => Ok(ColorName::Cyan),\n            \"sky\" => Ok(ColorName::Sky),\n            \"blue\" => Ok(ColorName::Blue),\n            \"indigo\" => Ok(ColorName::Indigo),\n            \"violet\" => Ok(ColorName::Violet),\n            \"purple\" => Ok(ColorName::Purple),\n            \"fuchsia\" => Ok(ColorName::Fuchsia),\n            \"pink\" => Ok(ColorName::Pink),\n            \"rose\" => Ok(ColorName::Rose),\n            _ => Err(anyhow::anyhow!(\"Invalid color name\")),\n        }\n    }\n}\n\nimpl TryFrom<SharedString> for ColorName {\n    type Error = anyhow::Error;\n    fn try_from(value: SharedString) -> std::result::Result<Self, Self::Error> {\n        value.as_ref().try_into()\n    }\n}\n\nimpl ColorName {\n    /// Returns all available color names.\n    pub fn all() -> [Self; 19] {\n        [\n            ColorName::Neutral,\n            ColorName::Gray,\n            ColorName::Red,\n            ColorName::Orange,\n            ColorName::Amber,\n            ColorName::Yellow,\n            ColorName::Lime,\n            ColorName::Green,\n            ColorName::Emerald,\n            ColorName::Teal,\n            ColorName::Cyan,\n            ColorName::Sky,\n            ColorName::Blue,\n            ColorName::Indigo,\n            ColorName::Violet,\n            ColorName::Purple,\n            ColorName::Fuchsia,\n            ColorName::Pink,\n            ColorName::Rose,\n        ]\n    }\n\n    /// Returns the color for the given scale.\n    ///\n    /// The `scale` is any of `[50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]`\n    /// falls back to 500 if out of range.\n    pub fn scale(&self, scale: usize) -> Hsla {\n        if self == &ColorName::White {\n            return DEFAULT_COLORS.white.hsla;\n        }\n        if self == &ColorName::Black {\n            return DEFAULT_COLORS.black.hsla;\n        }\n\n        let colors = match self {\n            ColorName::Neutral => &DEFAULT_COLORS.neutral,\n            ColorName::Gray => &DEFAULT_COLORS.gray,\n            ColorName::Red => &DEFAULT_COLORS.red,\n            ColorName::Orange => &DEFAULT_COLORS.orange,\n            ColorName::Amber => &DEFAULT_COLORS.amber,\n            ColorName::Yellow => &DEFAULT_COLORS.yellow,\n            ColorName::Lime => &DEFAULT_COLORS.lime,\n            ColorName::Green => &DEFAULT_COLORS.green,\n            ColorName::Emerald => &DEFAULT_COLORS.emerald,\n            ColorName::Teal => &DEFAULT_COLORS.teal,\n            ColorName::Cyan => &DEFAULT_COLORS.cyan,\n            ColorName::Sky => &DEFAULT_COLORS.sky,\n            ColorName::Blue => &DEFAULT_COLORS.blue,\n            ColorName::Indigo => &DEFAULT_COLORS.indigo,\n            ColorName::Violet => &DEFAULT_COLORS.violet,\n            ColorName::Purple => &DEFAULT_COLORS.purple,\n            ColorName::Fuchsia => &DEFAULT_COLORS.fuchsia,\n            ColorName::Pink => &DEFAULT_COLORS.pink,\n            ColorName::Rose => &DEFAULT_COLORS.rose,\n            _ => unreachable!(),\n        };\n\n        if let Some(color) = colors.get(&scale) {\n            color.hsla\n        } else {\n            colors.get(&500).unwrap().hsla\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]\npub(crate) struct ShadcnColors {\n    pub(crate) black: ShadcnColor,\n    pub(crate) white: ShadcnColor,\n    #[serde(with = \"color_scales\")]\n    pub(crate) slate: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) gray: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) zinc: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) neutral: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) stone: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) red: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) orange: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) amber: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) yellow: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) lime: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) green: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) emerald: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) teal: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) cyan: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) sky: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) blue: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) indigo: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) violet: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) purple: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) fuchsia: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) pink: ColorScales,\n    #[serde(with = \"color_scales\")]\n    pub(crate) rose: ColorScales,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Deserialize)]\npub(crate) struct ShadcnColor {\n    #[serde(default)]\n    pub(crate) scale: usize,\n    #[serde(deserialize_with = \"from_hsl_channel\", alias = \"hslChannel\")]\n    pub(crate) hsla: Hsla,\n}\n\n/// Deserialize Hsla from a string in the format \"210 40% 98%\"\nfn from_hsl_channel<'de, D>(deserializer: D) -> Result<Hsla, D::Error>\nwhere\n    D: Deserializer<'de>,\n{\n    let s: String = Deserialize::deserialize(deserializer).unwrap();\n\n    let mut parts = s.split_whitespace();\n    if parts.clone().count() != 3 {\n        return Err(D::Error::custom(\n            \"expected hslChannel has 3 parts, e.g: '210 40% 98%'\",\n        ));\n    }\n\n    fn parse_number(s: &str) -> f32 {\n        s.trim_end_matches('%')\n            .parse()\n            .expect(\"failed to parse number\")\n    }\n\n    let (h, s, l) = (\n        parse_number(parts.next().unwrap()),\n        parse_number(parts.next().unwrap()),\n        parse_number(parts.next().unwrap()),\n    );\n\n    Ok(hsl(h, s, l))\n}\n\nmacro_rules! color_method {\n    ($color:tt, $scale:tt) => {\n        paste::paste! {\n            #[inline]\n            #[allow(unused)]\n            pub fn [<$color _ $scale>]() -> Hsla {\n                if let Some(color) = DEFAULT_COLORS.$color.get(&($scale as usize)) {\n                    return color.hsla;\n                }\n\n                black()\n            }\n        }\n    };\n}\n\nmacro_rules! color_methods {\n    ($color:tt) => {\n        paste::paste! {\n            /// Get color by scale number.\n            ///\n            /// The possible scale numbers are:\n            /// 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950\n            ///\n            /// If the scale number is not found, it will return black color.\n            #[inline]\n            pub fn [<$color>](scale: usize) -> Hsla {\n                if let Some(color) = DEFAULT_COLORS.$color.get(&scale) {\n                    return color.hsla;\n                }\n\n                black()\n            }\n        }\n\n        color_method!($color, 50);\n        color_method!($color, 100);\n        color_method!($color, 200);\n        color_method!($color, 300);\n        color_method!($color, 400);\n        color_method!($color, 500);\n        color_method!($color, 600);\n        color_method!($color, 700);\n        color_method!($color, 800);\n        color_method!($color, 900);\n        color_method!($color, 950);\n    };\n}\n\npub fn black() -> Hsla {\n    DEFAULT_COLORS.black.hsla\n}\n\npub fn white() -> Hsla {\n    DEFAULT_COLORS.white.hsla\n}\n\ncolor_methods!(slate);\ncolor_methods!(gray);\ncolor_methods!(zinc);\ncolor_methods!(neutral);\ncolor_methods!(stone);\ncolor_methods!(red);\ncolor_methods!(orange);\ncolor_methods!(amber);\ncolor_methods!(yellow);\ncolor_methods!(lime);\ncolor_methods!(green);\ncolor_methods!(emerald);\ncolor_methods!(teal);\ncolor_methods!(cyan);\ncolor_methods!(sky);\ncolor_methods!(blue);\ncolor_methods!(indigo);\ncolor_methods!(violet);\ncolor_methods!(purple);\ncolor_methods!(fuchsia);\ncolor_methods!(pink);\ncolor_methods!(rose);\n\n/// Try to parse the color, HEX or [Tailwind Color](https://tailwindcss.com/docs/colors) expression.\n///\n/// # Parameter `color` should be one string value listed below:\n///\n/// - `#RRGGBB` - The HEX color string.\n/// - `#RRGGBBAA` - The HEX color string with alpha.\n///\n/// Or the Tailwind Color format:\n///\n/// - `name` - The color name `black`, `white`, or any other defined in `crate::color`.\n/// - `name-scale` - The color name with scale.\n/// - `name/opacity` - The color name with opacity, `opacity` should be an integer between 0 and 100.\n/// - `name-scale/opacity` - The color name with scale and opacity.\n///\npub fn try_parse_color(color: &str) -> Result<Hsla> {\n    if color.starts_with(\"#\") {\n        let rgba = gpui::Rgba::try_from(color)?;\n        return Ok(rgba.into());\n    }\n\n    let mut name = String::new();\n    let mut scale = None;\n    let mut opacity = None;\n    // 0: name, 1: scale, 2: opacity\n    let mut state = 0;\n    let mut part = String::new();\n\n    for c in color.chars() {\n        match c {\n            '-' if state == 0 => {\n                name = std::mem::take(&mut part);\n                state = 1;\n            }\n            '/' if state <= 1 => {\n                if state == 0 {\n                    name = std::mem::take(&mut part);\n                } else if state == 1 {\n                    scale = part.parse::<usize>().ok();\n                    part.clear();\n                }\n                state = 2;\n            }\n            _ => part.push(c),\n        }\n    }\n\n    match state {\n        0 => name = part,\n        1 => scale = part.parse::<usize>().ok(),\n        2 => opacity = part.parse::<f32>().ok(),\n        _ => {}\n    }\n\n    if name.is_empty() {\n        return Err(anyhow!(\"Empty color name\"));\n    }\n\n    let mut hsla = match name.as_str() {\n        \"black\" => Ok::<Hsla, Error>(crate::black()),\n        \"white\" => Ok(crate::white()),\n        _ => {\n            let color_name = ColorName::try_from(name.as_str())?;\n            if let Some(scale) = scale {\n                Ok(color_name.scale(scale))\n            } else {\n                Ok(color_name.scale(500))\n            }\n        }\n    }?;\n\n    if let Some(opacity) = opacity {\n        if opacity > 100. {\n            return Err(anyhow!(\"Invalid color opacity\"));\n        }\n        hsla = hsla.opacity(opacity / 100.);\n    }\n\n    Ok(hsla)\n}\n\n#[cfg(test)]\nmod tests {\n    use gpui::{rgb, rgba};\n\n    use super::*;\n\n    #[test]\n    fn test_default_colors() {\n        assert_eq!(white(), hsl(0.0, 0.0, 100.0));\n        assert_eq!(black(), hsl(0.0, 0.0, 0.0));\n\n        assert_eq!(slate_50(), hsl(210.0, 40.0, 98.0));\n        assert_eq!(slate_100(), hsl(210.0, 40.0, 96.1));\n        assert_eq!(slate_900(), hsl(222.2, 47.4, 11.2));\n\n        assert_eq!(red_50(), hsl(0.0, 85.7, 97.3));\n        assert_eq!(yellow_100(), hsl(54.9, 96.7, 88.0));\n        assert_eq!(green_200(), hsl(141.0, 78.9, 85.1));\n        assert_eq!(cyan_300(), hsl(187.0, 92.4, 69.0));\n        assert_eq!(blue_400(), hsl(213.1, 93.9, 67.8));\n        assert_eq!(indigo_500(), hsl(238.7, 83.5, 66.7));\n    }\n\n    #[test]\n    fn test_to_hex_string() {\n        let color: Hsla = rgb(0xf8fafc).into();\n        assert_eq!(color.to_hex(), \"#F8FAFC\");\n\n        let color: Hsla = rgb(0xfef2f2).into();\n        assert_eq!(color.to_hex(), \"#FEF2F2\");\n\n        let color: Hsla = rgba(0x0413fcaa).into();\n        assert_eq!(color.to_hex(), \"#0413FCAA\");\n    }\n\n    #[test]\n    fn test_from_hex_string() {\n        let color: Hsla = Hsla::parse_hex(\"#F8FAFC\").unwrap();\n        assert_eq!(color, rgb(0xf8fafc).into());\n\n        let color: Hsla = Hsla::parse_hex(\"#FEF2F2\").unwrap();\n        assert_eq!(color, rgb(0xfef2f2).into());\n\n        let color: Hsla = Hsla::parse_hex(\"#0413FCAA\").unwrap();\n        assert_eq!(color, rgba(0x0413fcaa).into());\n    }\n\n    #[test]\n    fn test_lighten() {\n        let color = super::hsl(240.0, 5.0, 30.0);\n        let color = color.lighten(0.5);\n        assert_eq!(color.l, 0.45000002);\n        let color = color.lighten(0.5);\n        assert_eq!(color.l, 0.675);\n        let color = color.lighten(0.1);\n        assert_eq!(color.l, 0.7425);\n    }\n\n    #[test]\n    fn test_darken() {\n        let color = super::hsl(240.0, 5.0, 96.0);\n        let color = color.darken(0.5);\n        assert_eq!(color.l, 0.48);\n        let color = color.darken(0.5);\n        assert_eq!(color.l, 0.24);\n    }\n\n    #[test]\n    fn test_mix() {\n        let red = Hsla::parse_hex(\"#FF0000\").unwrap();\n        let blue = Hsla::parse_hex(\"#0000FF\").unwrap();\n        let green = Hsla::parse_hex(\"#00FF00\").unwrap();\n        let yellow = Hsla::parse_hex(\"#FFFF00\").unwrap();\n\n        assert_eq!(red.mix(blue, 0.5).to_hex(), \"#FF00FF\");\n        assert_eq!(green.mix(red, 0.5).to_hex(), \"#FFFF00\");\n        assert_eq!(blue.mix(yellow, 0.2).to_hex(), \"#0098FF\");\n    }\n\n    #[test]\n    fn test_mix_oklab() {\n        let red = Hsla::parse_hex(\"#FF0000\").unwrap();\n        let blue = Hsla::parse_hex(\"#0000FF\").unwrap();\n        let transparent = gpui::Hsla {\n            h: 0.0,\n            s: 0.0,\n            l: 0.0,\n            a: 0.0,\n        };\n\n        // Test mixing red with transparent (similar to CSS color-mix example)\n        // color-mix(in oklab, red 20%, transparent) should give red with 20% opacity\n        let result = red.mix_oklab(transparent, 0.2);\n        assert!((result.a - 0.2).abs() < 0.01); // Alpha should be 20%\n\n        // The color should remain red (hue should be preserved)\n        let rgb_result = result.to_rgb();\n        let rgb_red = red.to_rgb();\n        // Allow some tolerance due to color space conversions\n        assert!(\n            (rgb_result.r - rgb_red.r).abs() < 0.05,\n            \"Red channel should be preserved\"\n        );\n        assert!(rgb_result.g < 0.05, \"Green channel should be near 0\");\n        assert!(rgb_result.b < 0.05, \"Blue channel should be near 0\");\n\n        // Test basic color mixing in Oklab space\n        let purple = red.mix_oklab(blue, 0.5);\n        // Oklab mixing should produce different results than HSL mixing\n        let purple_hsl = red.mix(blue, 0.5);\n        assert_ne!(purple.to_hex(), purple_hsl.to_hex());\n\n        // Test factor boundaries (allowing small floating point errors)\n        let result_0 = red.mix_oklab(blue, 0.0);\n        let result_1 = red.mix_oklab(blue, 1.0);\n\n        // Check that result is close to expected (within 1 color unit per channel)\n        let rgb_0 = result_0.to_rgb();\n        let rgb_blue = blue.to_rgb();\n        assert!((rgb_0.r - rgb_blue.r).abs() < 0.01);\n        assert!((rgb_0.g - rgb_blue.g).abs() < 0.01);\n        assert!((rgb_0.b - rgb_blue.b).abs() < 0.01);\n\n        let rgb_1 = result_1.to_rgb();\n        let rgb_red = red.to_rgb();\n        assert!((rgb_1.r - rgb_red.r).abs() < 0.01);\n        assert!((rgb_1.g - rgb_red.g).abs() < 0.01);\n        assert!((rgb_1.b - rgb_red.b).abs() < 0.01);\n    }\n\n    #[test]\n    fn test_color_name() {\n        assert_eq!(ColorName::Purple.to_string(), \"Purple\");\n        assert_eq!(format!(\"{}\", ColorName::Green), \"Green\");\n        assert_eq!(format!(\"{:?}\", ColorName::Yellow), \"Yellow\");\n\n        let color = ColorName::Green;\n        assert_eq!(color.scale(500).to_hex(), \"#21C55E\");\n        assert_eq!(color.scale(1500).to_hex(), \"#21C55E\");\n\n        for name in ColorName::all().iter() {\n            let name1: ColorName = name.to_string().as_str().try_into().unwrap();\n            assert_eq!(name1, *name);\n        }\n    }\n\n    #[test]\n    fn test_h_s_l() {\n        let color = hsl(260., 94., 80.);\n        assert_eq!(color.hue(200. / 360.), hsl(200., 94., 80.));\n        assert_eq!(color.saturation(74. / 100.), hsl(260., 74., 80.));\n        assert_eq!(color.lightness(74. / 100.), hsl(260., 94., 74.));\n    }\n\n    #[test]\n    fn test_try_parse_color() {\n        assert_eq!(\n            try_parse_color(\"#F2F200\").ok(),\n            Some(hsla(0.16666667, 1., 0.4745098, 1.0))\n        );\n        assert_eq!(\n            try_parse_color(\"#00f21888\").ok(),\n            Some(hsla(0.34986225, 1.0, 0.4745098, 0.53333336))\n        );\n        assert_eq!(try_parse_color(\"black\").ok(), Some(crate::black()));\n        assert_eq!(try_parse_color(\"white-800\").ok(), Some(crate::white()));\n        assert_eq!(try_parse_color(\"red\").ok(), Some(crate::red_500()));\n        assert_eq!(try_parse_color(\"blue-600\").ok(), Some(crate::blue_600()));\n        assert_eq!(\n            try_parse_color(\"pink/33\").ok(),\n            Some(crate::pink_500().opacity(0.33))\n        );\n        assert_eq!(\n            try_parse_color(\"orange-300/66\").ok(),\n            Some(crate::orange_300().opacity(0.66))\n        );\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/theme/default-colors.json",
    "content": "{\n  \"inherit\": \"inherit\",\n  \"_doc\": \"https://github.com/shadcn-ui/ui/blob/a46eea77a6680fc40cee9fa1f209e77931068f35/apps/v4/public/r/colors/index.json\",\n  \"current\": \"currentColor\",\n  \"transparent\": \"transparent\",\n  \"black\": {\n    \"hex\": \"#000000\",\n    \"rgb\": \"rgb(0,0,0)\",\n    \"hsl\": \"hsl(0,0%,0%)\",\n    \"oklch\": \"oklch(0.00,0.00,0)\",\n    \"rgbChannel\": \"0 0 0\",\n    \"hslChannel\": \"0 0% 0%\"\n  },\n  \"white\": {\n    \"hex\": \"#ffffff\",\n    \"rgb\": \"rgb(255,255,255)\",\n    \"hsl\": \"hsl(0,0%,100%)\",\n    \"oklch\": \"oklch(1.00,0.00,0)\",\n    \"rgbChannel\": \"255 255 255\",\n    \"hslChannel\": \"0 0% 100%\"\n  },\n  \"slate\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#f8fafc\",\n      \"rgb\": \"rgb(248,250,252)\",\n      \"hsl\": \"hsl(210,40%,98%)\",\n      \"oklch\": \"oklch(0.98,0.00,248)\",\n      \"rgbChannel\": \"248 250 252\",\n      \"hslChannel\": \"210 40% 98%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#f1f5f9\",\n      \"rgb\": \"rgb(241,245,249)\",\n      \"hsl\": \"hsl(210,40%,96.1%)\",\n      \"oklch\": \"oklch(0.97,0.01,248)\",\n      \"rgbChannel\": \"241 245 249\",\n      \"hslChannel\": \"210 40% 96.1%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#e2e8f0\",\n      \"rgb\": \"rgb(226,232,240)\",\n      \"hsl\": \"hsl(214.3,31.8%,91.4%)\",\n      \"oklch\": \"oklch(0.93,0.01,256)\",\n      \"rgbChannel\": \"226 232 240\",\n      \"hslChannel\": \"214.3 31.8% 91.4%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#cbd5e1\",\n      \"rgb\": \"rgb(203,213,225)\",\n      \"hsl\": \"hsl(212.7,26.8%,83.9%)\",\n      \"oklch\": \"oklch(0.87,0.02,253)\",\n      \"rgbChannel\": \"203 213 225\",\n      \"hslChannel\": \"212.7 26.8% 83.9%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#94a3b8\",\n      \"rgb\": \"rgb(148,163,184)\",\n      \"hsl\": \"hsl(215,20.2%,65.1%)\",\n      \"oklch\": \"oklch(0.71,0.04,257)\",\n      \"rgbChannel\": \"148 163 184\",\n      \"hslChannel\": \"215 20.2% 65.1%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#64748b\",\n      \"rgb\": \"rgb(100,116,139)\",\n      \"hsl\": \"hsl(215.4,16.3%,46.9%)\",\n      \"oklch\": \"oklch(0.55,0.04,257)\",\n      \"rgbChannel\": \"100 116 139\",\n      \"hslChannel\": \"215.4 16.3% 46.9%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#475569\",\n      \"rgb\": \"rgb(71,85,105)\",\n      \"hsl\": \"hsl(215.3,19.3%,34.5%)\",\n      \"oklch\": \"oklch(0.45,0.04,257)\",\n      \"rgbChannel\": \"71 85 105\",\n      \"hslChannel\": \"215.3 19.3% 34.5%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#334155\",\n      \"rgb\": \"rgb(51,65,85)\",\n      \"hsl\": \"hsl(215.3,25%,26.7%)\",\n      \"oklch\": \"oklch(0.37,0.04,257)\",\n      \"rgbChannel\": \"51 65 85\",\n      \"hslChannel\": \"215.3 25% 26.7%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#1e293b\",\n      \"rgb\": \"rgb(30,41,59)\",\n      \"hsl\": \"hsl(217.2,32.6%,17.5%)\",\n      \"oklch\": \"oklch(0.28,0.04,260)\",\n      \"rgbChannel\": \"30 41 59\",\n      \"hslChannel\": \"217.2 32.6% 17.5%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#0f172a\",\n      \"rgb\": \"rgb(15,23,42)\",\n      \"hsl\": \"hsl(222.2,47.4%,11.2%)\",\n      \"oklch\": \"oklch(0.21,0.04,266)\",\n      \"rgbChannel\": \"15 23 42\",\n      \"hslChannel\": \"222.2 47.4% 11.2%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#020617\",\n      \"rgb\": \"rgb(2,6,23)\",\n      \"hsl\": \"hsl(222.2,84%,4.9%)\",\n      \"oklch\": \"oklch(0.13,0.04,265)\",\n      \"rgbChannel\": \"2 6 23\",\n      \"hslChannel\": \"222.2 84% 4.9%\"\n    }\n  ],\n  \"gray\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#f9fafb\",\n      \"rgb\": \"rgb(249,250,251)\",\n      \"hsl\": \"hsl(210,20%,98%)\",\n      \"oklch\": \"oklch(0.98,0.00,248)\",\n      \"rgbChannel\": \"249 250 251\",\n      \"hslChannel\": \"210 20% 98%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#f3f4f6\",\n      \"rgb\": \"rgb(243,244,246)\",\n      \"hsl\": \"hsl(220,14.3%,95.9%)\",\n      \"oklch\": \"oklch(0.97,0.00,265)\",\n      \"rgbChannel\": \"243 244 246\",\n      \"hslChannel\": \"220 14.3% 95.9%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#e5e7eb\",\n      \"rgb\": \"rgb(229,231,235)\",\n      \"hsl\": \"hsl(220,13%,91%)\",\n      \"oklch\": \"oklch(0.93,0.01,265)\",\n      \"rgbChannel\": \"229 231 235\",\n      \"hslChannel\": \"220 13% 91%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#d1d5db\",\n      \"rgb\": \"rgb(209,213,219)\",\n      \"hsl\": \"hsl(216,12.2%,83.9%)\",\n      \"oklch\": \"oklch(0.87,0.01,258)\",\n      \"rgbChannel\": \"209 213 219\",\n      \"hslChannel\": \"216 12.2% 83.9%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#9ca3af\",\n      \"rgb\": \"rgb(156,163,175)\",\n      \"hsl\": \"hsl(217.9,10.6%,64.9%)\",\n      \"oklch\": \"oklch(0.71,0.02,261)\",\n      \"rgbChannel\": \"156 163 175\",\n      \"hslChannel\": \"217.9 10.6% 64.9%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#6b7280\",\n      \"rgb\": \"rgb(107,114,128)\",\n      \"hsl\": \"hsl(220,8.9%,46.1%)\",\n      \"oklch\": \"oklch(0.55,0.02,264)\",\n      \"rgbChannel\": \"107 114 128\",\n      \"hslChannel\": \"220 8.9% 46.1%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#4b5563\",\n      \"rgb\": \"rgb(75,85,99)\",\n      \"hsl\": \"hsl(215,13.8%,34.1%)\",\n      \"oklch\": \"oklch(0.45,0.03,257)\",\n      \"rgbChannel\": \"75 85 99\",\n      \"hslChannel\": \"215 13.8% 34.1%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#374151\",\n      \"rgb\": \"rgb(55,65,81)\",\n      \"hsl\": \"hsl(216.9,19.1%,26.7%)\",\n      \"oklch\": \"oklch(0.37,0.03,260)\",\n      \"rgbChannel\": \"55 65 81\",\n      \"hslChannel\": \"216.9 19.1% 26.7%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#1f2937\",\n      \"rgb\": \"rgb(31,41,55)\",\n      \"hsl\": \"hsl(215,27.9%,16.9%)\",\n      \"oklch\": \"oklch(0.28,0.03,257)\",\n      \"rgbChannel\": \"31 41 55\",\n      \"hslChannel\": \"215 27.9% 16.9%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#111827\",\n      \"rgb\": \"rgb(17,24,39)\",\n      \"hsl\": \"hsl(220.9,39.3%,11%)\",\n      \"oklch\": \"oklch(0.21,0.03,265)\",\n      \"rgbChannel\": \"17 24 39\",\n      \"hslChannel\": \"220.9 39.3% 11%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#030712\",\n      \"rgb\": \"rgb(3,7,18)\",\n      \"hsl\": \"hsl(224,71.4%,4.1%)\",\n      \"oklch\": \"oklch(0.13,0.03,262)\",\n      \"rgbChannel\": \"3 7 18\",\n      \"hslChannel\": \"224 71.4% 4.1%\"\n    }\n  ],\n  \"zinc\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#fafafa\",\n      \"rgb\": \"rgb(250,250,250)\",\n      \"hsl\": \"hsl(0,0%,98%)\",\n      \"oklch\": \"oklch(0.99,0.00,0)\",\n      \"rgbChannel\": \"250 250 250\",\n      \"hslChannel\": \"0 0% 98%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#f4f4f5\",\n      \"rgb\": \"rgb(244,244,245)\",\n      \"hsl\": \"hsl(240,4.8%,95.9%)\",\n      \"oklch\": \"oklch(0.97,0.00,286)\",\n      \"rgbChannel\": \"244 244 245\",\n      \"hslChannel\": \"240 4.8% 95.9%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#e4e4e7\",\n      \"rgb\": \"rgb(228,228,231)\",\n      \"hsl\": \"hsl(240,5.9%,90%)\",\n      \"oklch\": \"oklch(0.92,0.00,286)\",\n      \"rgbChannel\": \"228 228 231\",\n      \"hslChannel\": \"240 5.9% 90%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#d4d4d8\",\n      \"rgb\": \"rgb(212,212,216)\",\n      \"hsl\": \"hsl(240,4.9%,83.9%)\",\n      \"oklch\": \"oklch(0.87,0.01,286)\",\n      \"rgbChannel\": \"212 212 216\",\n      \"hslChannel\": \"240 4.9% 83.9%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#a1a1aa\",\n      \"rgb\": \"rgb(161,161,170)\",\n      \"hsl\": \"hsl(240,5%,64.9%)\",\n      \"oklch\": \"oklch(0.71,0.01,286)\",\n      \"rgbChannel\": \"161 161 170\",\n      \"hslChannel\": \"240 5% 64.9%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#71717a\",\n      \"rgb\": \"rgb(113,113,122)\",\n      \"hsl\": \"hsl(240,3.8%,46.1%)\",\n      \"oklch\": \"oklch(0.55,0.01,286)\",\n      \"rgbChannel\": \"113 113 122\",\n      \"hslChannel\": \"240 3.8% 46.1%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#52525b\",\n      \"rgb\": \"rgb(82,82,91)\",\n      \"hsl\": \"hsl(240,5.2%,33.9%)\",\n      \"oklch\": \"oklch(0.44,0.01,286)\",\n      \"rgbChannel\": \"82 82 91\",\n      \"hslChannel\": \"240 5.2% 33.9%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#3f3f46\",\n      \"rgb\": \"rgb(63,63,70)\",\n      \"hsl\": \"hsl(240,5.3%,26.1%)\",\n      \"oklch\": \"oklch(0.37,0.01,286)\",\n      \"rgbChannel\": \"63 63 70\",\n      \"hslChannel\": \"240 5.3% 26.1%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#27272a\",\n      \"rgb\": \"rgb(39,39,42)\",\n      \"hsl\": \"hsl(240,3.7%,15.9%)\",\n      \"oklch\": \"oklch(0.27,0.01,286)\",\n      \"rgbChannel\": \"39 39 42\",\n      \"hslChannel\": \"240 3.7% 15.9%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#18181b\",\n      \"rgb\": \"rgb(24,24,27)\",\n      \"hsl\": \"hsl(240,5.9%,10%)\",\n      \"oklch\": \"oklch(0.21,0.01,286)\",\n      \"rgbChannel\": \"24 24 27\",\n      \"hslChannel\": \"240 5.9% 10%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#09090b\",\n      \"rgb\": \"rgb(9,9,11)\",\n      \"hsl\": \"hsl(240,10%,3.9%)\",\n      \"oklch\": \"oklch(0.14,0.00,286)\",\n      \"rgbChannel\": \"9 9 11\",\n      \"hslChannel\": \"240 10% 3.9%\"\n    }\n  ],\n  \"neutral\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#fafafa\",\n      \"rgb\": \"rgb(250,250,250)\",\n      \"hsl\": \"hsl(0,0%,98%)\",\n      \"oklch\": \"oklch(0.99,0.00,0)\",\n      \"rgbChannel\": \"250 250 250\",\n      \"hslChannel\": \"0 0% 98%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#f5f5f5\",\n      \"rgb\": \"rgb(245,245,245)\",\n      \"hsl\": \"hsl(0,0%,96.1%)\",\n      \"oklch\": \"oklch(0.97,0.00,0)\",\n      \"rgbChannel\": \"245 245 245\",\n      \"hslChannel\": \"0 0% 96.1%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#e5e5e5\",\n      \"rgb\": \"rgb(229,229,229)\",\n      \"hsl\": \"hsl(0,0%,89.8%)\",\n      \"oklch\": \"oklch(0.92,0.00,0)\",\n      \"rgbChannel\": \"229 229 229\",\n      \"hslChannel\": \"0 0% 89.8%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#d4d4d4\",\n      \"rgb\": \"rgb(212,212,212)\",\n      \"hsl\": \"hsl(0,0%,83.1%)\",\n      \"oklch\": \"oklch(0.87,0.00,0)\",\n      \"rgbChannel\": \"212 212 212\",\n      \"hslChannel\": \"0 0% 83.1%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#a3a3a3\",\n      \"rgb\": \"rgb(163,163,163)\",\n      \"hsl\": \"hsl(0,0%,63.9%)\",\n      \"oklch\": \"oklch(0.72,0.00,0)\",\n      \"rgbChannel\": \"163 163 163\",\n      \"hslChannel\": \"0 0% 63.9%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#737373\",\n      \"rgb\": \"rgb(115,115,115)\",\n      \"hsl\": \"hsl(0,0%,45.1%)\",\n      \"oklch\": \"oklch(0.56,0.00,0)\",\n      \"rgbChannel\": \"115 115 115\",\n      \"hslChannel\": \"0 0% 45.1%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#525252\",\n      \"rgb\": \"rgb(82,82,82)\",\n      \"hsl\": \"hsl(0,0%,32.2%)\",\n      \"oklch\": \"oklch(0.44,0.00,0)\",\n      \"rgbChannel\": \"82 82 82\",\n      \"hslChannel\": \"0 0% 32.2%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#404040\",\n      \"rgb\": \"rgb(64,64,64)\",\n      \"hsl\": \"hsl(0,0%,25.1%)\",\n      \"oklch\": \"oklch(0.37,0.00,0)\",\n      \"rgbChannel\": \"64 64 64\",\n      \"hslChannel\": \"0 0% 25.1%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#262626\",\n      \"rgb\": \"rgb(38,38,38)\",\n      \"hsl\": \"hsl(0,0%,14.9%)\",\n      \"oklch\": \"oklch(0.27,0.00,0)\",\n      \"rgbChannel\": \"38 38 38\",\n      \"hslChannel\": \"0 0% 14.9%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#171717\",\n      \"rgb\": \"rgb(23,23,23)\",\n      \"hsl\": \"hsl(0,0%,9%)\",\n      \"oklch\": \"oklch(0.20,0.00,0)\",\n      \"rgbChannel\": \"23 23 23\",\n      \"hslChannel\": \"0 0% 9%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#0a0a0a\",\n      \"rgb\": \"rgb(10,10,10)\",\n      \"hsl\": \"hsl(0,0%,3.9%)\",\n      \"oklch\": \"oklch(0.14,0.00,0)\",\n      \"rgbChannel\": \"10 10 10\",\n      \"hslChannel\": \"0 0% 3.9%\"\n    }\n  ],\n  \"stone\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#fafaf9\",\n      \"rgb\": \"rgb(250,250,249)\",\n      \"hsl\": \"hsl(60,9.1%,97.8%)\",\n      \"oklch\": \"oklch(0.98,0.00,106)\",\n      \"rgbChannel\": \"250 250 249\",\n      \"hslChannel\": \"60 9.1% 97.8%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#f5f5f4\",\n      \"rgb\": \"rgb(245,245,244)\",\n      \"hsl\": \"hsl(60,4.8%,95.9%)\",\n      \"oklch\": \"oklch(0.97,0.00,106)\",\n      \"rgbChannel\": \"245 245 244\",\n      \"hslChannel\": \"60 4.8% 95.9%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#e7e5e4\",\n      \"rgb\": \"rgb(231,229,228)\",\n      \"hsl\": \"hsl(20,5.9%,90%)\",\n      \"oklch\": \"oklch(0.92,0.00,49)\",\n      \"rgbChannel\": \"231 229 228\",\n      \"hslChannel\": \"20 5.9% 90%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#d6d3d1\",\n      \"rgb\": \"rgb(214,211,209)\",\n      \"hsl\": \"hsl(24,5.7%,82.9%)\",\n      \"oklch\": \"oklch(0.87,0.00,56)\",\n      \"rgbChannel\": \"214 211 209\",\n      \"hslChannel\": \"24 5.7% 82.9%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#a8a29e\",\n      \"rgb\": \"rgb(168,162,158)\",\n      \"hsl\": \"hsl(24,5.4%,63.9%)\",\n      \"oklch\": \"oklch(0.72,0.01,56)\",\n      \"rgbChannel\": \"168 162 158\",\n      \"hslChannel\": \"24 5.4% 63.9%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#78716c\",\n      \"rgb\": \"rgb(120,113,108)\",\n      \"hsl\": \"hsl(25,5.3%,44.7%)\",\n      \"oklch\": \"oklch(0.55,0.01,58)\",\n      \"rgbChannel\": \"120 113 108\",\n      \"hslChannel\": \"25 5.3% 44.7%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#57534e\",\n      \"rgb\": \"rgb(87,83,78)\",\n      \"hsl\": \"hsl(33.3,5.5%,32.4%)\",\n      \"oklch\": \"oklch(0.44,0.01,74)\",\n      \"rgbChannel\": \"87 83 78\",\n      \"hslChannel\": \"33.3 5.5% 32.4%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#44403c\",\n      \"rgb\": \"rgb(68,64,60)\",\n      \"hsl\": \"hsl(30,6.3%,25.1%)\",\n      \"oklch\": \"oklch(0.37,0.01,68)\",\n      \"rgbChannel\": \"68 64 60\",\n      \"hslChannel\": \"30 6.3% 25.1%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#292524\",\n      \"rgb\": \"rgb(41,37,36)\",\n      \"hsl\": \"hsl(12,6.5%,15.1%)\",\n      \"oklch\": \"oklch(0.27,0.01,34)\",\n      \"rgbChannel\": \"41 37 36\",\n      \"hslChannel\": \"12 6.5% 15.1%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#1c1917\",\n      \"rgb\": \"rgb(28,25,23)\",\n      \"hsl\": \"hsl(24,9.8%,10%)\",\n      \"oklch\": \"oklch(0.22,0.01,56)\",\n      \"rgbChannel\": \"28 25 23\",\n      \"hslChannel\": \"24 9.8% 10%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#0c0a09\",\n      \"rgb\": \"rgb(12,10,9)\",\n      \"hsl\": \"hsl(20,14.3%,4.1%)\",\n      \"oklch\": \"oklch(0.15,0.00,49)\",\n      \"rgbChannel\": \"12 10 9\",\n      \"hslChannel\": \"20 14.3% 4.1%\"\n    }\n  ],\n  \"red\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#fef2f2\",\n      \"rgb\": \"rgb(254,242,242)\",\n      \"hsl\": \"hsl(0,85.7%,97.3%)\",\n      \"oklch\": \"oklch(0.97,0.01,17)\",\n      \"rgbChannel\": \"254 242 242\",\n      \"hslChannel\": \"0 85.7% 97.3%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#fee2e2\",\n      \"rgb\": \"rgb(254,226,226)\",\n      \"hsl\": \"hsl(0,93.3%,94.1%)\",\n      \"oklch\": \"oklch(0.94,0.03,18)\",\n      \"rgbChannel\": \"254 226 226\",\n      \"hslChannel\": \"0 93.3% 94.1%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#fecaca\",\n      \"rgb\": \"rgb(254,202,202)\",\n      \"hsl\": \"hsl(0,96.3%,89.4%)\",\n      \"oklch\": \"oklch(0.88,0.06,18)\",\n      \"rgbChannel\": \"254 202 202\",\n      \"hslChannel\": \"0 96.3% 89.4%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#fca5a5\",\n      \"rgb\": \"rgb(252,165,165)\",\n      \"hsl\": \"hsl(0,93.5%,81.8%)\",\n      \"oklch\": \"oklch(0.81,0.10,20)\",\n      \"rgbChannel\": \"252 165 165\",\n      \"hslChannel\": \"0 93.5% 81.8%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#f87171\",\n      \"rgb\": \"rgb(248,113,113)\",\n      \"hsl\": \"hsl(0,90.6%,70.8%)\",\n      \"oklch\": \"oklch(0.71,0.17,22)\",\n      \"rgbChannel\": \"248 113 113\",\n      \"hslChannel\": \"0 90.6% 70.8%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#ef4444\",\n      \"rgb\": \"rgb(239,68,68)\",\n      \"hsl\": \"hsl(0,84.2%,60.2%)\",\n      \"oklch\": \"oklch(0.64,0.21,25)\",\n      \"rgbChannel\": \"239 68 68\",\n      \"hslChannel\": \"0 84.2% 60.2%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#dc2626\",\n      \"rgb\": \"rgb(220,38,38)\",\n      \"hsl\": \"hsl(0,72.2%,50.6%)\",\n      \"oklch\": \"oklch(0.58,0.22,27)\",\n      \"rgbChannel\": \"220 38 38\",\n      \"hslChannel\": \"0 72.2% 50.6%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#b91c1c\",\n      \"rgb\": \"rgb(185,28,28)\",\n      \"hsl\": \"hsl(0,73.7%,41.8%)\",\n      \"oklch\": \"oklch(0.51,0.19,28)\",\n      \"rgbChannel\": \"185 28 28\",\n      \"hslChannel\": \"0 73.7% 41.8%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#991b1b\",\n      \"rgb\": \"rgb(153,27,27)\",\n      \"hsl\": \"hsl(0,70%,35.3%)\",\n      \"oklch\": \"oklch(0.44,0.16,27)\",\n      \"rgbChannel\": \"153 27 27\",\n      \"hslChannel\": \"0 70% 35.3%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#7f1d1d\",\n      \"rgb\": \"rgb(127,29,29)\",\n      \"hsl\": \"hsl(0,62.8%,30.6%)\",\n      \"oklch\": \"oklch(0.40,0.13,26)\",\n      \"rgbChannel\": \"127 29 29\",\n      \"hslChannel\": \"0 62.8% 30.6%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#450a0a\",\n      \"rgb\": \"rgb(69,10,10)\",\n      \"hsl\": \"hsl(0,74.7%,15.5%)\",\n      \"oklch\": \"oklch(0.26,0.09,26)\",\n      \"rgbChannel\": \"69 10 10\",\n      \"hslChannel\": \"0 74.7% 15.5%\"\n    }\n  ],\n  \"orange\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#fff7ed\",\n      \"rgb\": \"rgb(255,247,237)\",\n      \"hsl\": \"hsl(33.3,100%,96.5%)\",\n      \"oklch\": \"oklch(0.98,0.02,74)\",\n      \"rgbChannel\": \"255 247 237\",\n      \"hslChannel\": \"33.3 100% 96.5%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#ffedd5\",\n      \"rgb\": \"rgb(255,237,213)\",\n      \"hsl\": \"hsl(34.3,100%,91.8%)\",\n      \"oklch\": \"oklch(0.95,0.04,75)\",\n      \"rgbChannel\": \"255 237 213\",\n      \"hslChannel\": \"34.3 100% 91.8%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#fed7aa\",\n      \"rgb\": \"rgb(254,215,170)\",\n      \"hsl\": \"hsl(32.1,97.7%,83.1%)\",\n      \"oklch\": \"oklch(0.90,0.07,71)\",\n      \"rgbChannel\": \"254 215 170\",\n      \"hslChannel\": \"32.1 97.7% 83.1%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#fdba74\",\n      \"rgb\": \"rgb(253,186,116)\",\n      \"hsl\": \"hsl(30.7,97.2%,72.4%)\",\n      \"oklch\": \"oklch(0.84,0.12,66)\",\n      \"rgbChannel\": \"253 186 116\",\n      \"hslChannel\": \"30.7 97.2% 72.4%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#fb923c\",\n      \"rgb\": \"rgb(251,146,60)\",\n      \"hsl\": \"hsl(27,96%,61%)\",\n      \"oklch\": \"oklch(0.76,0.16,56)\",\n      \"rgbChannel\": \"251 146 60\",\n      \"hslChannel\": \"27 96% 61%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#f97316\",\n      \"rgb\": \"rgb(249,115,22)\",\n      \"hsl\": \"hsl(24.6,95%,53.1%)\",\n      \"oklch\": \"oklch(0.70,0.19,48)\",\n      \"rgbChannel\": \"249 115 22\",\n      \"hslChannel\": \"24.6 95% 53.1%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#ea580c\",\n      \"rgb\": \"rgb(234,88,12)\",\n      \"hsl\": \"hsl(20.5,90.2%,48.2%)\",\n      \"oklch\": \"oklch(0.65,0.19,41)\",\n      \"rgbChannel\": \"234 88 12\",\n      \"hslChannel\": \"20.5 90.2% 48.2%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#c2410c\",\n      \"rgb\": \"rgb(194,65,12)\",\n      \"hsl\": \"hsl(17.5,88.3%,40.4%)\",\n      \"oklch\": \"oklch(0.55,0.17,38)\",\n      \"rgbChannel\": \"194 65 12\",\n      \"hslChannel\": \"17.5 88.3% 40.4%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#9a3412\",\n      \"rgb\": \"rgb(154,52,18)\",\n      \"hsl\": \"hsl(15,79.1%,33.7%)\",\n      \"oklch\": \"oklch(0.47,0.14,37)\",\n      \"rgbChannel\": \"154 52 18\",\n      \"hslChannel\": \"15 79.1% 33.7%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#7c2d12\",\n      \"rgb\": \"rgb(124,45,18)\",\n      \"hsl\": \"hsl(15.3,74.6%,27.8%)\",\n      \"oklch\": \"oklch(0.41,0.12,38)\",\n      \"rgbChannel\": \"124 45 18\",\n      \"hslChannel\": \"15.3 74.6% 27.8%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#431407\",\n      \"rgb\": \"rgb(67,20,7)\",\n      \"hsl\": \"hsl(13,81.1%,14.5%)\",\n      \"oklch\": \"oklch(0.27,0.08,36)\",\n      \"rgbChannel\": \"67 20 7\",\n      \"hslChannel\": \"13 81.1% 14.5%\"\n    }\n  ],\n  \"amber\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#fffbeb\",\n      \"rgb\": \"rgb(255,251,235)\",\n      \"hsl\": \"hsl(48,100%,96.1%)\",\n      \"oklch\": \"oklch(0.99,0.02,95)\",\n      \"rgbChannel\": \"255 251 235\",\n      \"hslChannel\": \"48 100% 96.1%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#fef3c7\",\n      \"rgb\": \"rgb(254,243,199)\",\n      \"hsl\": \"hsl(48,96.5%,88.8%)\",\n      \"oklch\": \"oklch(0.96,0.06,96)\",\n      \"rgbChannel\": \"254 243 199\",\n      \"hslChannel\": \"48 96.5% 88.8%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#fde68a\",\n      \"rgb\": \"rgb(253,230,138)\",\n      \"hsl\": \"hsl(48,96.6%,76.7%)\",\n      \"oklch\": \"oklch(0.92,0.12,96)\",\n      \"rgbChannel\": \"253 230 138\",\n      \"hslChannel\": \"48 96.6% 76.7%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#fcd34d\",\n      \"rgb\": \"rgb(252,211,77)\",\n      \"hsl\": \"hsl(45.9,96.7%,64.5%)\",\n      \"oklch\": \"oklch(0.88,0.15,92)\",\n      \"rgbChannel\": \"252 211 77\",\n      \"hslChannel\": \"45.9 96.7% 64.5%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#fbbf24\",\n      \"rgb\": \"rgb(251,191,36)\",\n      \"hsl\": \"hsl(43.3,96.4%,56.3%)\",\n      \"oklch\": \"oklch(0.84,0.16,84)\",\n      \"rgbChannel\": \"251 191 36\",\n      \"hslChannel\": \"43.3 96.4% 56.3%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#f59e0b\",\n      \"rgb\": \"rgb(245,158,11)\",\n      \"hsl\": \"hsl(37.7,92.1%,50.2%)\",\n      \"oklch\": \"oklch(0.77,0.16,70)\",\n      \"rgbChannel\": \"245 158 11\",\n      \"hslChannel\": \"37.7 92.1% 50.2%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#d97706\",\n      \"rgb\": \"rgb(217,119,6)\",\n      \"hsl\": \"hsl(32.1,94.6%,43.7%)\",\n      \"oklch\": \"oklch(0.67,0.16,58)\",\n      \"rgbChannel\": \"217 119 6\",\n      \"hslChannel\": \"32.1 94.6% 43.7%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#b45309\",\n      \"rgb\": \"rgb(180,83,9)\",\n      \"hsl\": \"hsl(26,90.5%,37.1%)\",\n      \"oklch\": \"oklch(0.56,0.15,49)\",\n      \"rgbChannel\": \"180 83 9\",\n      \"hslChannel\": \"26 90.5% 37.1%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#92400e\",\n      \"rgb\": \"rgb(146,64,14)\",\n      \"hsl\": \"hsl(22.7,82.5%,31.4%)\",\n      \"oklch\": \"oklch(0.47,0.12,46)\",\n      \"rgbChannel\": \"146 64 14\",\n      \"hslChannel\": \"22.7 82.5% 31.4%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#78350f\",\n      \"rgb\": \"rgb(120,53,15)\",\n      \"hsl\": \"hsl(21.7,77.8%,26.5%)\",\n      \"oklch\": \"oklch(0.41,0.11,46)\",\n      \"rgbChannel\": \"120 53 15\",\n      \"hslChannel\": \"21.7 77.8% 26.5%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#451a03\",\n      \"rgb\": \"rgb(69,26,3)\",\n      \"hsl\": \"hsl(20.9,91.7%,14.1%)\",\n      \"oklch\": \"oklch(0.28,0.07,46)\",\n      \"rgbChannel\": \"69 26 3\",\n      \"hslChannel\": \"20.9 91.7% 14.1%\"\n    }\n  ],\n  \"yellow\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#fefce8\",\n      \"rgb\": \"rgb(254,252,232)\",\n      \"hsl\": \"hsl(54.5,91.7%,95.3%)\",\n      \"oklch\": \"oklch(0.99,0.03,102)\",\n      \"rgbChannel\": \"254 252 232\",\n      \"hslChannel\": \"54.5 91.7% 95.3%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#fef9c3\",\n      \"rgb\": \"rgb(254,249,195)\",\n      \"hsl\": \"hsl(54.9,96.7%,88%)\",\n      \"oklch\": \"oklch(0.97,0.07,103)\",\n      \"rgbChannel\": \"254 249 195\",\n      \"hslChannel\": \"54.9 96.7% 88%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#fef08a\",\n      \"rgb\": \"rgb(254,240,138)\",\n      \"hsl\": \"hsl(52.8,98.3%,76.9%)\",\n      \"oklch\": \"oklch(0.95,0.12,102)\",\n      \"rgbChannel\": \"254 240 138\",\n      \"hslChannel\": \"52.8 98.3% 76.9%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#fde047\",\n      \"rgb\": \"rgb(253,224,71)\",\n      \"hsl\": \"hsl(50.4,97.8%,63.5%)\",\n      \"oklch\": \"oklch(0.91,0.17,98)\",\n      \"rgbChannel\": \"253 224 71\",\n      \"hslChannel\": \"50.4 97.8% 63.5%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#facc15\",\n      \"rgb\": \"rgb(250,204,21)\",\n      \"hsl\": \"hsl(47.9,95.8%,53.1%)\",\n      \"oklch\": \"oklch(0.86,0.17,92)\",\n      \"rgbChannel\": \"250 204 21\",\n      \"hslChannel\": \"47.9 95.8% 53.1%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#eab308\",\n      \"rgb\": \"rgb(234,179,8)\",\n      \"hsl\": \"hsl(45.4,93.4%,47.5%)\",\n      \"oklch\": \"oklch(0.80,0.16,86)\",\n      \"rgbChannel\": \"234 179 8\",\n      \"hslChannel\": \"45.4 93.4% 47.5%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#ca8a04\",\n      \"rgb\": \"rgb(202,138,4)\",\n      \"hsl\": \"hsl(40.6,96.1%,40.4%)\",\n      \"oklch\": \"oklch(0.68,0.14,76)\",\n      \"rgbChannel\": \"202 138 4\",\n      \"hslChannel\": \"40.6 96.1% 40.4%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#a16207\",\n      \"rgb\": \"rgb(161,98,7)\",\n      \"hsl\": \"hsl(35.5,91.7%,32.9%)\",\n      \"oklch\": \"oklch(0.55,0.12,66)\",\n      \"rgbChannel\": \"161 98 7\",\n      \"hslChannel\": \"35.5 91.7% 32.9%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#854d0e\",\n      \"rgb\": \"rgb(133,77,14)\",\n      \"hsl\": \"hsl(31.8,81%,28.8%)\",\n      \"oklch\": \"oklch(0.48,0.10,62)\",\n      \"rgbChannel\": \"133 77 14\",\n      \"hslChannel\": \"31.8 81% 28.8%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#713f12\",\n      \"rgb\": \"rgb(113,63,18)\",\n      \"hsl\": \"hsl(28.4,72.5%,25.7%)\",\n      \"oklch\": \"oklch(0.42,0.09,58)\",\n      \"rgbChannel\": \"113 63 18\",\n      \"hslChannel\": \"28.4 72.5% 25.7%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#422006\",\n      \"rgb\": \"rgb(66,32,6)\",\n      \"hsl\": \"hsl(26,83.3%,14.1%)\",\n      \"oklch\": \"oklch(0.29,0.06,54)\",\n      \"rgbChannel\": \"66 32 6\",\n      \"hslChannel\": \"26 83.3% 14.1%\"\n    }\n  ],\n  \"lime\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#f7fee7\",\n      \"rgb\": \"rgb(247,254,231)\",\n      \"hsl\": \"hsl(78.3,92%,95.1%)\",\n      \"oklch\": \"oklch(0.99,0.03,121)\",\n      \"rgbChannel\": \"247 254 231\",\n      \"hslChannel\": \"78.3 92% 95.1%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#ecfccb\",\n      \"rgb\": \"rgb(236,252,203)\",\n      \"hsl\": \"hsl(79.6,89.1%,89.2%)\",\n      \"oklch\": \"oklch(0.97,0.07,122)\",\n      \"rgbChannel\": \"236 252 203\",\n      \"hslChannel\": \"79.6 89.1% 89.2%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#d9f99d\",\n      \"rgb\": \"rgb(217,249,157)\",\n      \"hsl\": \"hsl(80.9,88.5%,79.6%)\",\n      \"oklch\": \"oklch(0.94,0.12,124)\",\n      \"rgbChannel\": \"217 249 157\",\n      \"hslChannel\": \"80.9 88.5% 79.6%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#bef264\",\n      \"rgb\": \"rgb(190,242,100)\",\n      \"hsl\": \"hsl(82,84.5%,67.1%)\",\n      \"oklch\": \"oklch(0.90,0.18,127)\",\n      \"rgbChannel\": \"190 242 100\",\n      \"hslChannel\": \"82 84.5% 67.1%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#a3e635\",\n      \"rgb\": \"rgb(163,230,53)\",\n      \"hsl\": \"hsl(82.7,78%,55.5%)\",\n      \"oklch\": \"oklch(0.85,0.21,129)\",\n      \"rgbChannel\": \"163 230 53\",\n      \"hslChannel\": \"82.7 78% 55.5%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#84cc16\",\n      \"rgb\": \"rgb(132,204,22)\",\n      \"hsl\": \"hsl(83.7,80.5%,44.3%)\",\n      \"oklch\": \"oklch(0.77,0.20,131)\",\n      \"rgbChannel\": \"132 204 22\",\n      \"hslChannel\": \"83.7 80.5% 44.3%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#65a30d\",\n      \"rgb\": \"rgb(101,163,13)\",\n      \"hsl\": \"hsl(84.8,85.2%,34.5%)\",\n      \"oklch\": \"oklch(0.65,0.18,132)\",\n      \"rgbChannel\": \"101 163 13\",\n      \"hslChannel\": \"84.8 85.2% 34.5%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#4d7c0f\",\n      \"rgb\": \"rgb(77,124,15)\",\n      \"hsl\": \"hsl(85.9,78.4%,27.3%)\",\n      \"oklch\": \"oklch(0.53,0.14,132)\",\n      \"rgbChannel\": \"77 124 15\",\n      \"hslChannel\": \"85.9 78.4% 27.3%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#3f6212\",\n      \"rgb\": \"rgb(63,98,18)\",\n      \"hsl\": \"hsl(86.3,69%,22.7%)\",\n      \"oklch\": \"oklch(0.45,0.11,131)\",\n      \"rgbChannel\": \"63 98 18\",\n      \"hslChannel\": \"86.3 69% 22.7%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#365314\",\n      \"rgb\": \"rgb(54,83,20)\",\n      \"hsl\": \"hsl(87.6,61.2%,20.2%)\",\n      \"oklch\": \"oklch(0.41,0.10,131)\",\n      \"rgbChannel\": \"54 83 20\",\n      \"hslChannel\": \"87.6 61.2% 20.2%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#1a2e05\",\n      \"rgb\": \"rgb(26,46,5)\",\n      \"hsl\": \"hsl(89.3,80.4%,10%)\",\n      \"oklch\": \"oklch(0.27,0.07,132)\",\n      \"rgbChannel\": \"26 46 5\",\n      \"hslChannel\": \"89.3 80.4% 10%\"\n    }\n  ],\n  \"green\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#f0fdf4\",\n      \"rgb\": \"rgb(240,253,244)\",\n      \"hsl\": \"hsl(138.5,76.5%,96.7%)\",\n      \"oklch\": \"oklch(0.98,0.02,156)\",\n      \"rgbChannel\": \"240 253 244\",\n      \"hslChannel\": \"138.5 76.5% 96.7%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#dcfce7\",\n      \"rgb\": \"rgb(220,252,231)\",\n      \"hsl\": \"hsl(140.6,84.2%,92.5%)\",\n      \"oklch\": \"oklch(0.96,0.04,157)\",\n      \"rgbChannel\": \"220 252 231\",\n      \"hslChannel\": \"140.6 84.2% 92.5%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#bbf7d0\",\n      \"rgb\": \"rgb(187,247,208)\",\n      \"hsl\": \"hsl(141,78.9%,85.1%)\",\n      \"oklch\": \"oklch(0.93,0.08,156)\",\n      \"rgbChannel\": \"187 247 208\",\n      \"hslChannel\": \"141 78.9% 85.1%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#86efac\",\n      \"rgb\": \"rgb(134,239,172)\",\n      \"hsl\": \"hsl(141.7,76.6%,73.1%)\",\n      \"oklch\": \"oklch(0.87,0.14,154)\",\n      \"rgbChannel\": \"134 239 172\",\n      \"hslChannel\": \"141.7 76.6% 73.1%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#4ade80\",\n      \"rgb\": \"rgb(74,222,128)\",\n      \"hsl\": \"hsl(141.9,69.2%,58%)\",\n      \"oklch\": \"oklch(0.80,0.18,152)\",\n      \"rgbChannel\": \"74 222 128\",\n      \"hslChannel\": \"141.9 69.2% 58%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#22c55e\",\n      \"rgb\": \"rgb(34,197,94)\",\n      \"hsl\": \"hsl(142.1,70.6%,45.3%)\",\n      \"oklch\": \"oklch(0.72,0.19,150)\",\n      \"rgbChannel\": \"34 197 94\",\n      \"hslChannel\": \"142.1 70.6% 45.3%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#16a34a\",\n      \"rgb\": \"rgb(22,163,74)\",\n      \"hsl\": \"hsl(142.1,76.2%,36.3%)\",\n      \"oklch\": \"oklch(0.63,0.17,149)\",\n      \"rgbChannel\": \"22 163 74\",\n      \"hslChannel\": \"142.1 76.2% 36.3%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#15803d\",\n      \"rgb\": \"rgb(21,128,61)\",\n      \"hsl\": \"hsl(142.4,71.8%,29.2%)\",\n      \"oklch\": \"oklch(0.53,0.14,150)\",\n      \"rgbChannel\": \"21 128 61\",\n      \"hslChannel\": \"142.4 71.8% 29.2%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#166534\",\n      \"rgb\": \"rgb(22,101,52)\",\n      \"hsl\": \"hsl(142.8,64.2%,24.1%)\",\n      \"oklch\": \"oklch(0.45,0.11,151)\",\n      \"rgbChannel\": \"22 101 52\",\n      \"hslChannel\": \"142.8 64.2% 24.1%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#14532d\",\n      \"rgb\": \"rgb(20,83,45)\",\n      \"hsl\": \"hsl(143.8,61.2%,20.2%)\",\n      \"oklch\": \"oklch(0.39,0.09,153)\",\n      \"rgbChannel\": \"20 83 45\",\n      \"hslChannel\": \"143.8 61.2% 20.2%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#052e16\",\n      \"rgb\": \"rgb(5,46,22)\",\n      \"hsl\": \"hsl(144.9,80.4%,10%)\",\n      \"oklch\": \"oklch(0.27,0.06,153)\",\n      \"rgbChannel\": \"5 46 22\",\n      \"hslChannel\": \"144.9 80.4% 10%\"\n    }\n  ],\n  \"emerald\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#ecfdf5\",\n      \"rgb\": \"rgb(236,253,245)\",\n      \"hsl\": \"hsl(151.8,81%,95.9%)\",\n      \"oklch\": \"oklch(0.98,0.02,166)\",\n      \"rgbChannel\": \"236 253 245\",\n      \"hslChannel\": \"151.8 81% 95.9%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#d1fae5\",\n      \"rgb\": \"rgb(209,250,229)\",\n      \"hsl\": \"hsl(149.3,80.4%,90%)\",\n      \"oklch\": \"oklch(0.95,0.05,163)\",\n      \"rgbChannel\": \"209 250 229\",\n      \"hslChannel\": \"149.3 80.4% 90%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#a7f3d0\",\n      \"rgb\": \"rgb(167,243,208)\",\n      \"hsl\": \"hsl(152.4,76%,80.4%)\",\n      \"oklch\": \"oklch(0.90,0.09,164)\",\n      \"rgbChannel\": \"167 243 208\",\n      \"hslChannel\": \"152.4 76% 80.4%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#6ee7b7\",\n      \"rgb\": \"rgb(110,231,183)\",\n      \"hsl\": \"hsl(156.2,71.6%,66.9%)\",\n      \"oklch\": \"oklch(0.85,0.13,165)\",\n      \"rgbChannel\": \"110 231 183\",\n      \"hslChannel\": \"156.2 71.6% 66.9%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#34d399\",\n      \"rgb\": \"rgb(52,211,153)\",\n      \"hsl\": \"hsl(158.1,64.4%,51.6%)\",\n      \"oklch\": \"oklch(0.77,0.15,163)\",\n      \"rgbChannel\": \"52 211 153\",\n      \"hslChannel\": \"158.1 64.4% 51.6%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#10b981\",\n      \"rgb\": \"rgb(16,185,129)\",\n      \"hsl\": \"hsl(160.1,84.1%,39.4%)\",\n      \"oklch\": \"oklch(0.70,0.15,162)\",\n      \"rgbChannel\": \"16 185 129\",\n      \"hslChannel\": \"160.1 84.1% 39.4%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#059669\",\n      \"rgb\": \"rgb(5,150,105)\",\n      \"hsl\": \"hsl(161.4,93.5%,30.4%)\",\n      \"oklch\": \"oklch(0.60,0.13,163)\",\n      \"rgbChannel\": \"5 150 105\",\n      \"hslChannel\": \"161.4 93.5% 30.4%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#047857\",\n      \"rgb\": \"rgb(4,120,87)\",\n      \"hsl\": \"hsl(162.9,93.5%,24.3%)\",\n      \"oklch\": \"oklch(0.51,0.10,166)\",\n      \"rgbChannel\": \"4 120 87\",\n      \"hslChannel\": \"162.9 93.5% 24.3%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#065f46\",\n      \"rgb\": \"rgb(6,95,70)\",\n      \"hsl\": \"hsl(163.1,88.1%,19.8%)\",\n      \"oklch\": \"oklch(0.43,0.09,167)\",\n      \"rgbChannel\": \"6 95 70\",\n      \"hslChannel\": \"163.1 88.1% 19.8%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#064e3b\",\n      \"rgb\": \"rgb(6,78,59)\",\n      \"hsl\": \"hsl(164.2,85.7%,16.5%)\",\n      \"oklch\": \"oklch(0.38,0.07,169)\",\n      \"rgbChannel\": \"6 78 59\",\n      \"hslChannel\": \"164.2 85.7% 16.5%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#022c22\",\n      \"rgb\": \"rgb(2,44,34)\",\n      \"hsl\": \"hsl(165.7,91.3%,9%)\",\n      \"oklch\": \"oklch(0.26,0.05,173)\",\n      \"rgbChannel\": \"2 44 34\",\n      \"hslChannel\": \"165.7 91.3% 9%\"\n    }\n  ],\n  \"teal\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#f0fdfa\",\n      \"rgb\": \"rgb(240,253,250)\",\n      \"hsl\": \"hsl(166.2,76.5%,96.7%)\",\n      \"oklch\": \"oklch(0.98,0.01,181)\",\n      \"rgbChannel\": \"240 253 250\",\n      \"hslChannel\": \"166.2 76.5% 96.7%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#ccfbf1\",\n      \"rgb\": \"rgb(204,251,241)\",\n      \"hsl\": \"hsl(167.2,85.5%,89.2%)\",\n      \"oklch\": \"oklch(0.95,0.05,181)\",\n      \"rgbChannel\": \"204 251 241\",\n      \"hslChannel\": \"167.2 85.5% 89.2%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#99f6e4\",\n      \"rgb\": \"rgb(153,246,228)\",\n      \"hsl\": \"hsl(168.4,83.8%,78.2%)\",\n      \"oklch\": \"oklch(0.91,0.09,180)\",\n      \"rgbChannel\": \"153 246 228\",\n      \"hslChannel\": \"168.4 83.8% 78.2%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#5eead4\",\n      \"rgb\": \"rgb(94,234,212)\",\n      \"hsl\": \"hsl(170.6,76.9%,64.3%)\",\n      \"oklch\": \"oklch(0.85,0.13,181)\",\n      \"rgbChannel\": \"94 234 212\",\n      \"hslChannel\": \"170.6 76.9% 64.3%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#2dd4bf\",\n      \"rgb\": \"rgb(45,212,191)\",\n      \"hsl\": \"hsl(172.5,66%,50.4%)\",\n      \"oklch\": \"oklch(0.78,0.13,182)\",\n      \"rgbChannel\": \"45 212 191\",\n      \"hslChannel\": \"172.5 66% 50.4%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#14b8a6\",\n      \"rgb\": \"rgb(20,184,166)\",\n      \"hsl\": \"hsl(173.4,80.4%,40%)\",\n      \"oklch\": \"oklch(0.70,0.12,183)\",\n      \"rgbChannel\": \"20 184 166\",\n      \"hslChannel\": \"173.4 80.4% 40%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#0d9488\",\n      \"rgb\": \"rgb(13,148,136)\",\n      \"hsl\": \"hsl(174.7,83.9%,31.6%)\",\n      \"oklch\": \"oklch(0.60,0.10,185)\",\n      \"rgbChannel\": \"13 148 136\",\n      \"hslChannel\": \"174.7 83.9% 31.6%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#0f766e\",\n      \"rgb\": \"rgb(15,118,110)\",\n      \"hsl\": \"hsl(175.3,77.4%,26.1%)\",\n      \"oklch\": \"oklch(0.51,0.09,186)\",\n      \"rgbChannel\": \"15 118 110\",\n      \"hslChannel\": \"175.3 77.4% 26.1%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#115e59\",\n      \"rgb\": \"rgb(17,94,89)\",\n      \"hsl\": \"hsl(176.1,69.4%,21.8%)\",\n      \"oklch\": \"oklch(0.44,0.07,188)\",\n      \"rgbChannel\": \"17 94 89\",\n      \"hslChannel\": \"176.1 69.4% 21.8%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#134e4a\",\n      \"rgb\": \"rgb(19,78,74)\",\n      \"hsl\": \"hsl(175.9,60.8%,19%)\",\n      \"oklch\": \"oklch(0.39,0.06,188)\",\n      \"rgbChannel\": \"19 78 74\",\n      \"hslChannel\": \"175.9 60.8% 19%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#042f2e\",\n      \"rgb\": \"rgb(4,47,46)\",\n      \"hsl\": \"hsl(178.6,84.3%,10%)\",\n      \"oklch\": \"oklch(0.28,0.04,193)\",\n      \"rgbChannel\": \"4 47 46\",\n      \"hslChannel\": \"178.6 84.3% 10%\"\n    }\n  ],\n  \"cyan\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#ecfeff\",\n      \"rgb\": \"rgb(236,254,255)\",\n      \"hsl\": \"hsl(183.2,100%,96.3%)\",\n      \"oklch\": \"oklch(0.98,0.02,201)\",\n      \"rgbChannel\": \"236 254 255\",\n      \"hslChannel\": \"183.2 100% 96.3%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#cffafe\",\n      \"rgb\": \"rgb(207,250,254)\",\n      \"hsl\": \"hsl(185.1,95.9%,90.4%)\",\n      \"oklch\": \"oklch(0.96,0.04,203)\",\n      \"rgbChannel\": \"207 250 254\",\n      \"hslChannel\": \"185.1 95.9% 90.4%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#a5f3fc\",\n      \"rgb\": \"rgb(165,243,252)\",\n      \"hsl\": \"hsl(186.2,93.5%,81.8%)\",\n      \"oklch\": \"oklch(0.92,0.08,205)\",\n      \"rgbChannel\": \"165 243 252\",\n      \"hslChannel\": \"186.2 93.5% 81.8%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#67e8f9\",\n      \"rgb\": \"rgb(103,232,249)\",\n      \"hsl\": \"hsl(187,92.4%,69%)\",\n      \"oklch\": \"oklch(0.87,0.12,207)\",\n      \"rgbChannel\": \"103 232 249\",\n      \"hslChannel\": \"187 92.4% 69%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#22d3ee\",\n      \"rgb\": \"rgb(34,211,238)\",\n      \"hsl\": \"hsl(187.9,85.7%,53.3%)\",\n      \"oklch\": \"oklch(0.80,0.13,212)\",\n      \"rgbChannel\": \"34 211 238\",\n      \"hslChannel\": \"187.9 85.7% 53.3%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#06b6d4\",\n      \"rgb\": \"rgb(6,182,212)\",\n      \"hsl\": \"hsl(188.7,94.5%,42.7%)\",\n      \"oklch\": \"oklch(0.71,0.13,215)\",\n      \"rgbChannel\": \"6 182 212\",\n      \"hslChannel\": \"188.7 94.5% 42.7%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#0891b2\",\n      \"rgb\": \"rgb(8,145,178)\",\n      \"hsl\": \"hsl(191.6,91.4%,36.5%)\",\n      \"oklch\": \"oklch(0.61,0.11,222)\",\n      \"rgbChannel\": \"8 145 178\",\n      \"hslChannel\": \"191.6 91.4% 36.5%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#0e7490\",\n      \"rgb\": \"rgb(14,116,144)\",\n      \"hsl\": \"hsl(192.9,82.3%,31%)\",\n      \"oklch\": \"oklch(0.52,0.09,223)\",\n      \"rgbChannel\": \"14 116 144\",\n      \"hslChannel\": \"192.9 82.3% 31%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#155e75\",\n      \"rgb\": \"rgb(21,94,117)\",\n      \"hsl\": \"hsl(194.4,69.6%,27.1%)\",\n      \"oklch\": \"oklch(0.45,0.08,224)\",\n      \"rgbChannel\": \"21 94 117\",\n      \"hslChannel\": \"194.4 69.6% 27.1%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#164e63\",\n      \"rgb\": \"rgb(22,78,99)\",\n      \"hsl\": \"hsl(196.4,63.6%,23.7%)\",\n      \"oklch\": \"oklch(0.40,0.07,227)\",\n      \"rgbChannel\": \"22 78 99\",\n      \"hslChannel\": \"196.4 63.6% 23.7%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#083344\",\n      \"rgb\": \"rgb(8,51,68)\",\n      \"hsl\": \"hsl(197,78.9%,14.9%)\",\n      \"oklch\": \"oklch(0.30,0.05,230)\",\n      \"rgbChannel\": \"8 51 68\",\n      \"hslChannel\": \"197 78.9% 14.9%\"\n    }\n  ],\n  \"sky\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#f0f9ff\",\n      \"rgb\": \"rgb(240,249,255)\",\n      \"hsl\": \"hsl(204,100%,97.1%)\",\n      \"oklch\": \"oklch(0.98,0.01,237)\",\n      \"rgbChannel\": \"240 249 255\",\n      \"hslChannel\": \"204 100% 97.1%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#e0f2fe\",\n      \"rgb\": \"rgb(224,242,254)\",\n      \"hsl\": \"hsl(204,93.8%,93.7%)\",\n      \"oklch\": \"oklch(0.95,0.03,237)\",\n      \"rgbChannel\": \"224 242 254\",\n      \"hslChannel\": \"204 93.8% 93.7%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#bae6fd\",\n      \"rgb\": \"rgb(186,230,253)\",\n      \"hsl\": \"hsl(200.6,94.4%,86.1%)\",\n      \"oklch\": \"oklch(0.90,0.06,231)\",\n      \"rgbChannel\": \"186 230 253\",\n      \"hslChannel\": \"200.6 94.4% 86.1%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#7dd3fc\",\n      \"rgb\": \"rgb(125,211,252)\",\n      \"hsl\": \"hsl(199.4,95.5%,73.9%)\",\n      \"oklch\": \"oklch(0.83,0.10,230)\",\n      \"rgbChannel\": \"125 211 252\",\n      \"hslChannel\": \"199.4 95.5% 73.9%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#38bdf8\",\n      \"rgb\": \"rgb(56,189,248)\",\n      \"hsl\": \"hsl(198.4,93.2%,59.6%)\",\n      \"oklch\": \"oklch(0.75,0.14,233)\",\n      \"rgbChannel\": \"56 189 248\",\n      \"hslChannel\": \"198.4 93.2% 59.6%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#0ea5e9\",\n      \"rgb\": \"rgb(14,165,233)\",\n      \"hsl\": \"hsl(198.6,88.7%,48.4%)\",\n      \"oklch\": \"oklch(0.68,0.15,237)\",\n      \"rgbChannel\": \"14 165 233\",\n      \"hslChannel\": \"198.6 88.7% 48.4%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#0284c7\",\n      \"rgb\": \"rgb(2,132,199)\",\n      \"hsl\": \"hsl(200.4,98%,39.4%)\",\n      \"oklch\": \"oklch(0.59,0.14,242)\",\n      \"rgbChannel\": \"2 132 199\",\n      \"hslChannel\": \"200.4 98% 39.4%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#0369a1\",\n      \"rgb\": \"rgb(3,105,161)\",\n      \"hsl\": \"hsl(201.3,96.3%,32.2%)\",\n      \"oklch\": \"oklch(0.50,0.12,243)\",\n      \"rgbChannel\": \"3 105 161\",\n      \"hslChannel\": \"201.3 96.3% 32.2%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#075985\",\n      \"rgb\": \"rgb(7,89,133)\",\n      \"hsl\": \"hsl(201,90%,27.5%)\",\n      \"oklch\": \"oklch(0.44,0.10,241)\",\n      \"rgbChannel\": \"7 89 133\",\n      \"hslChannel\": \"201 90% 27.5%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#0c4a6e\",\n      \"rgb\": \"rgb(12,74,110)\",\n      \"hsl\": \"hsl(202,80.3%,23.9%)\",\n      \"oklch\": \"oklch(0.39,0.08,241)\",\n      \"rgbChannel\": \"12 74 110\",\n      \"hslChannel\": \"202 80.3% 23.9%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#082f49\",\n      \"rgb\": \"rgb(8,47,73)\",\n      \"hsl\": \"hsl(204,80.2%,15.9%)\",\n      \"oklch\": \"oklch(0.29,0.06,243)\",\n      \"rgbChannel\": \"8 47 73\",\n      \"hslChannel\": \"204 80.2% 15.9%\"\n    }\n  ],\n  \"blue\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#eff6ff\",\n      \"rgb\": \"rgb(239,246,255)\",\n      \"hsl\": \"hsl(213.8,100%,96.9%)\",\n      \"oklch\": \"oklch(0.97,0.01,255)\",\n      \"rgbChannel\": \"239 246 255\",\n      \"hslChannel\": \"213.8 100% 96.9%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#dbeafe\",\n      \"rgb\": \"rgb(219,234,254)\",\n      \"hsl\": \"hsl(214.3,94.6%,92.7%)\",\n      \"oklch\": \"oklch(0.93,0.03,256)\",\n      \"rgbChannel\": \"219 234 254\",\n      \"hslChannel\": \"214.3 94.6% 92.7%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#bfdbfe\",\n      \"rgb\": \"rgb(191,219,254)\",\n      \"hsl\": \"hsl(213.3,96.9%,87.3%)\",\n      \"oklch\": \"oklch(0.88,0.06,254)\",\n      \"rgbChannel\": \"191 219 254\",\n      \"hslChannel\": \"213.3 96.9% 87.3%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#93c5fd\",\n      \"rgb\": \"rgb(147,197,253)\",\n      \"hsl\": \"hsl(211.7,96.4%,78.4%)\",\n      \"oklch\": \"oklch(0.81,0.10,252)\",\n      \"rgbChannel\": \"147 197 253\",\n      \"hslChannel\": \"211.7 96.4% 78.4%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#60a5fa\",\n      \"rgb\": \"rgb(96,165,250)\",\n      \"hsl\": \"hsl(213.1,93.9%,67.8%)\",\n      \"oklch\": \"oklch(0.71,0.14,255)\",\n      \"rgbChannel\": \"96 165 250\",\n      \"hslChannel\": \"213.1 93.9% 67.8%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#3b82f6\",\n      \"rgb\": \"rgb(59,130,246)\",\n      \"hsl\": \"hsl(217.2,91.2%,59.8%)\",\n      \"oklch\": \"oklch(0.62,0.19,260)\",\n      \"rgbChannel\": \"59 130 246\",\n      \"hslChannel\": \"217.2 91.2% 59.8%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#2563eb\",\n      \"rgb\": \"rgb(37,99,235)\",\n      \"hsl\": \"hsl(221.2,83.2%,53.3%)\",\n      \"oklch\": \"oklch(0.55,0.22,263)\",\n      \"rgbChannel\": \"37 99 235\",\n      \"hslChannel\": \"221.2 83.2% 53.3%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#1d4ed8\",\n      \"rgb\": \"rgb(29,78,216)\",\n      \"hsl\": \"hsl(224.3,76.3%,48%)\",\n      \"oklch\": \"oklch(0.49,0.22,264)\",\n      \"rgbChannel\": \"29 78 216\",\n      \"hslChannel\": \"224.3 76.3% 48%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#1e40af\",\n      \"rgb\": \"rgb(30,64,175)\",\n      \"hsl\": \"hsl(225.9,70.7%,40.2%)\",\n      \"oklch\": \"oklch(0.42,0.18,266)\",\n      \"rgbChannel\": \"30 64 175\",\n      \"hslChannel\": \"225.9 70.7% 40.2%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#1e3a8a\",\n      \"rgb\": \"rgb(30,58,138)\",\n      \"hsl\": \"hsl(224.4,64.3%,32.9%)\",\n      \"oklch\": \"oklch(0.38,0.14,266)\",\n      \"rgbChannel\": \"30 58 138\",\n      \"hslChannel\": \"224.4 64.3% 32.9%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#172554\",\n      \"rgb\": \"rgb(23,37,84)\",\n      \"hsl\": \"hsl(226.2,57%,21%)\",\n      \"oklch\": \"oklch(0.28,0.09,268)\",\n      \"rgbChannel\": \"23 37 84\",\n      \"hslChannel\": \"226.2 57% 21%\"\n    }\n  ],\n  \"indigo\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#eef2ff\",\n      \"rgb\": \"rgb(238,242,255)\",\n      \"hsl\": \"hsl(225.9,100%,96.7%)\",\n      \"oklch\": \"oklch(0.96,0.02,272)\",\n      \"rgbChannel\": \"238 242 255\",\n      \"hslChannel\": \"225.9 100% 96.7%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#e0e7ff\",\n      \"rgb\": \"rgb(224,231,255)\",\n      \"hsl\": \"hsl(226.5,100%,93.9%)\",\n      \"oklch\": \"oklch(0.93,0.03,273)\",\n      \"rgbChannel\": \"224 231 255\",\n      \"hslChannel\": \"226.5 100% 93.9%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#c7d2fe\",\n      \"rgb\": \"rgb(199,210,254)\",\n      \"hsl\": \"hsl(228,96.5%,88.8%)\",\n      \"oklch\": \"oklch(0.87,0.06,274)\",\n      \"rgbChannel\": \"199 210 254\",\n      \"hslChannel\": \"228 96.5% 88.8%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#a5b4fc\",\n      \"rgb\": \"rgb(165,180,252)\",\n      \"hsl\": \"hsl(229.7,93.5%,81.8%)\",\n      \"oklch\": \"oklch(0.79,0.10,275)\",\n      \"rgbChannel\": \"165 180 252\",\n      \"hslChannel\": \"229.7 93.5% 81.8%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#818cf8\",\n      \"rgb\": \"rgb(129,140,248)\",\n      \"hsl\": \"hsl(234.5,89.5%,73.9%)\",\n      \"oklch\": \"oklch(0.68,0.16,277)\",\n      \"rgbChannel\": \"129 140 248\",\n      \"hslChannel\": \"234.5 89.5% 73.9%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#6366f1\",\n      \"rgb\": \"rgb(99,102,241)\",\n      \"hsl\": \"hsl(238.7,83.5%,66.7%)\",\n      \"oklch\": \"oklch(0.59,0.20,277)\",\n      \"rgbChannel\": \"99 102 241\",\n      \"hslChannel\": \"238.7 83.5% 66.7%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#4f46e5\",\n      \"rgb\": \"rgb(79,70,229)\",\n      \"hsl\": \"hsl(243.4,75.4%,58.6%)\",\n      \"oklch\": \"oklch(0.51,0.23,277)\",\n      \"rgbChannel\": \"79 70 229\",\n      \"hslChannel\": \"243.4 75.4% 58.6%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#4338ca\",\n      \"rgb\": \"rgb(67,56,202)\",\n      \"hsl\": \"hsl(244.5,57.9%,50.6%)\",\n      \"oklch\": \"oklch(0.46,0.21,277)\",\n      \"rgbChannel\": \"67 56 202\",\n      \"hslChannel\": \"244.5 57.9% 50.6%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#3730a3\",\n      \"rgb\": \"rgb(55,48,163)\",\n      \"hsl\": \"hsl(243.7,54.5%,41.4%)\",\n      \"oklch\": \"oklch(0.40,0.18,277)\",\n      \"rgbChannel\": \"55 48 163\",\n      \"hslChannel\": \"243.7 54.5% 41.4%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#312e81\",\n      \"rgb\": \"rgb(49,46,129)\",\n      \"hsl\": \"hsl(242.2,47.4%,34.3%)\",\n      \"oklch\": \"oklch(0.36,0.14,279)\",\n      \"rgbChannel\": \"49 46 129\",\n      \"hslChannel\": \"242.2 47.4% 34.3%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#1e1b4b\",\n      \"rgb\": \"rgb(30,27,75)\",\n      \"hsl\": \"hsl(243.8,47.1%,20%)\",\n      \"oklch\": \"oklch(0.26,0.09,281)\",\n      \"rgbChannel\": \"30 27 75\",\n      \"hslChannel\": \"243.8 47.1% 20%\"\n    }\n  ],\n  \"violet\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#f5f3ff\",\n      \"rgb\": \"rgb(245,243,255)\",\n      \"hsl\": \"hsl(250,100%,97.6%)\",\n      \"oklch\": \"oklch(0.97,0.02,294)\",\n      \"rgbChannel\": \"245 243 255\",\n      \"hslChannel\": \"250 100% 97.6%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#ede9fe\",\n      \"rgb\": \"rgb(237,233,254)\",\n      \"hsl\": \"hsl(251.4,91.3%,95.5%)\",\n      \"oklch\": \"oklch(0.94,0.03,295)\",\n      \"rgbChannel\": \"237 233 254\",\n      \"hslChannel\": \"251.4 91.3% 95.5%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#ddd6fe\",\n      \"rgb\": \"rgb(221,214,254)\",\n      \"hsl\": \"hsl(250.5,95.2%,91.8%)\",\n      \"oklch\": \"oklch(0.89,0.05,293)\",\n      \"rgbChannel\": \"221 214 254\",\n      \"hslChannel\": \"250.5 95.2% 91.8%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#c4b5fd\",\n      \"rgb\": \"rgb(196,181,253)\",\n      \"hsl\": \"hsl(252.5,94.7%,85.1%)\",\n      \"oklch\": \"oklch(0.81,0.10,294)\",\n      \"rgbChannel\": \"196 181 253\",\n      \"hslChannel\": \"252.5 94.7% 85.1%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#a78bfa\",\n      \"rgb\": \"rgb(167,139,250)\",\n      \"hsl\": \"hsl(255.1,91.7%,76.3%)\",\n      \"oklch\": \"oklch(0.71,0.16,294)\",\n      \"rgbChannel\": \"167 139 250\",\n      \"hslChannel\": \"255.1 91.7% 76.3%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#8b5cf6\",\n      \"rgb\": \"rgb(139,92,246)\",\n      \"hsl\": \"hsl(258.3,89.5%,66.3%)\",\n      \"oklch\": \"oklch(0.61,0.22,293)\",\n      \"rgbChannel\": \"139 92 246\",\n      \"hslChannel\": \"258.3 89.5% 66.3%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#7c3aed\",\n      \"rgb\": \"rgb(124,58,237)\",\n      \"hsl\": \"hsl(262.1,83.3%,57.8%)\",\n      \"oklch\": \"oklch(0.54,0.25,293)\",\n      \"rgbChannel\": \"124 58 237\",\n      \"hslChannel\": \"262.1 83.3% 57.8%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#6d28d9\",\n      \"rgb\": \"rgb(109,40,217)\",\n      \"hsl\": \"hsl(263.4,70%,50.4%)\",\n      \"oklch\": \"oklch(0.49,0.24,293)\",\n      \"rgbChannel\": \"109 40 217\",\n      \"hslChannel\": \"263.4 70% 50.4%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#5b21b6\",\n      \"rgb\": \"rgb(91,33,182)\",\n      \"hsl\": \"hsl(263.4,69.3%,42.2%)\",\n      \"oklch\": \"oklch(0.43,0.21,293)\",\n      \"rgbChannel\": \"91 33 182\",\n      \"hslChannel\": \"263.4 69.3% 42.2%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#4c1d95\",\n      \"rgb\": \"rgb(76,29,149)\",\n      \"hsl\": \"hsl(263.5,67.4%,34.9%)\",\n      \"oklch\": \"oklch(0.38,0.18,294)\",\n      \"rgbChannel\": \"76 29 149\",\n      \"hslChannel\": \"263.5 67.4% 34.9%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#1e1b4b\",\n      \"rgb\": \"rgb(46,16,101)\",\n      \"hsl\": \"hsl(261.2,72.6%,22.9%)\",\n      \"oklch\": \"oklch(0.28,0.14,291)\",\n      \"rgbChannel\": \"46 16 101\",\n      \"hslChannel\": \"261.2 72.6% 22.9%\"\n    }\n  ],\n  \"purple\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#faf5ff\",\n      \"rgb\": \"rgb(250,245,255)\",\n      \"hsl\": \"hsl(270,100%,98%)\",\n      \"oklch\": \"oklch(0.98,0.01,308)\",\n      \"rgbChannel\": \"250 245 255\",\n      \"hslChannel\": \"270 100% 98%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#f3e8ff\",\n      \"rgb\": \"rgb(243,232,255)\",\n      \"hsl\": \"hsl(268.7,100%,95.5%)\",\n      \"oklch\": \"oklch(0.95,0.03,307)\",\n      \"rgbChannel\": \"243 232 255\",\n      \"hslChannel\": \"268.7 100% 95.5%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#e9d5ff\",\n      \"rgb\": \"rgb(233,213,255)\",\n      \"hsl\": \"hsl(268.6,100%,91.8%)\",\n      \"oklch\": \"oklch(0.90,0.06,307)\",\n      \"rgbChannel\": \"233 213 255\",\n      \"hslChannel\": \"268.6 100% 91.8%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#d8b4fe\",\n      \"rgb\": \"rgb(216,180,254)\",\n      \"hsl\": \"hsl(269.2,97.4%,85.1%)\",\n      \"oklch\": \"oklch(0.83,0.11,306)\",\n      \"rgbChannel\": \"216 180 254\",\n      \"hslChannel\": \"269.2 97.4% 85.1%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#c084fc\",\n      \"rgb\": \"rgb(192,132,252)\",\n      \"hsl\": \"hsl(270,95.2%,75.3%)\",\n      \"oklch\": \"oklch(0.72,0.18,306)\",\n      \"rgbChannel\": \"192 132 252\",\n      \"hslChannel\": \"270 95.2% 75.3%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#a855f7\",\n      \"rgb\": \"rgb(168,85,247)\",\n      \"hsl\": \"hsl(270.7,91%,65.1%)\",\n      \"oklch\": \"oklch(0.63,0.23,304)\",\n      \"rgbChannel\": \"168 85 247\",\n      \"hslChannel\": \"270.7 91% 65.1%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#9333ea\",\n      \"rgb\": \"rgb(147,51,234)\",\n      \"hsl\": \"hsl(271.5,81.3%,55.9%)\",\n      \"oklch\": \"oklch(0.56,0.25,302)\",\n      \"rgbChannel\": \"147 51 234\",\n      \"hslChannel\": \"271.5 81.3% 55.9%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#7e22ce\",\n      \"rgb\": \"rgb(126,34,206)\",\n      \"hsl\": \"hsl(272.1,71.7%,47.1%)\",\n      \"oklch\": \"oklch(0.50,0.24,302)\",\n      \"rgbChannel\": \"126 34 206\",\n      \"hslChannel\": \"272.1 71.7% 47.1%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#6b21a8\",\n      \"rgb\": \"rgb(107,33,168)\",\n      \"hsl\": \"hsl(272.9,67.2%,39.4%)\",\n      \"oklch\": \"oklch(0.44,0.20,304)\",\n      \"rgbChannel\": \"107 33 168\",\n      \"hslChannel\": \"272.9 67.2% 39.4%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#581c87\",\n      \"rgb\": \"rgb(88,28,135)\",\n      \"hsl\": \"hsl(273.6,65.6%,32%)\",\n      \"oklch\": \"oklch(0.38,0.17,305)\",\n      \"rgbChannel\": \"88 28 135\",\n      \"hslChannel\": \"273.6 65.6% 32%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#3b0764\",\n      \"rgb\": \"rgb(59,7,100)\",\n      \"hsl\": \"hsl(273.5,86.9%,21%)\",\n      \"oklch\": \"oklch(0.29,0.14,303)\",\n      \"rgbChannel\": \"59 7 100\",\n      \"hslChannel\": \"273.5 86.9% 21%\"\n    }\n  ],\n  \"fuchsia\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#fdf4ff\",\n      \"rgb\": \"rgb(253,244,255)\",\n      \"hsl\": \"hsl(289.1,100%,97.8%)\",\n      \"oklch\": \"oklch(0.98,0.02,320)\",\n      \"rgbChannel\": \"253 244 255\",\n      \"hslChannel\": \"289.1 100% 97.8%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#fae8ff\",\n      \"rgb\": \"rgb(250,232,255)\",\n      \"hsl\": \"hsl(287,100%,95.5%)\",\n      \"oklch\": \"oklch(0.95,0.04,319)\",\n      \"rgbChannel\": \"250 232 255\",\n      \"hslChannel\": \"287 100% 95.5%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#f5d0fe\",\n      \"rgb\": \"rgb(245,208,254)\",\n      \"hsl\": \"hsl(288.3,95.8%,90.6%)\",\n      \"oklch\": \"oklch(0.90,0.07,320)\",\n      \"rgbChannel\": \"245 208 254\",\n      \"hslChannel\": \"288.3 95.8% 90.6%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#f0abfc\",\n      \"rgb\": \"rgb(240,171,252)\",\n      \"hsl\": \"hsl(291.1,93.1%,82.9%)\",\n      \"oklch\": \"oklch(0.83,0.13,321)\",\n      \"rgbChannel\": \"240 171 252\",\n      \"hslChannel\": \"291.1 93.1% 82.9%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#e879f9\",\n      \"rgb\": \"rgb(232,121,249)\",\n      \"hsl\": \"hsl(292,91.4%,72.5%)\",\n      \"oklch\": \"oklch(0.75,0.21,322)\",\n      \"rgbChannel\": \"232 121 249\",\n      \"hslChannel\": \"292 91.4% 72.5%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#d946ef\",\n      \"rgb\": \"rgb(217,70,239)\",\n      \"hsl\": \"hsl(292.2,84.1%,60.6%)\",\n      \"oklch\": \"oklch(0.67,0.26,322)\",\n      \"rgbChannel\": \"217 70 239\",\n      \"hslChannel\": \"292.2 84.1% 60.6%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#c026d3\",\n      \"rgb\": \"rgb(192,38,211)\",\n      \"hsl\": \"hsl(293.4,69.5%,48.8%)\",\n      \"oklch\": \"oklch(0.59,0.26,323)\",\n      \"rgbChannel\": \"192 38 211\",\n      \"hslChannel\": \"293.4 69.5% 48.8%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#a21caf\",\n      \"rgb\": \"rgb(162,28,175)\",\n      \"hsl\": \"hsl(294.7,72.4%,39.8%)\",\n      \"oklch\": \"oklch(0.52,0.23,324)\",\n      \"rgbChannel\": \"162 28 175\",\n      \"hslChannel\": \"294.7 72.4% 39.8%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#86198f\",\n      \"rgb\": \"rgb(134,25,143)\",\n      \"hsl\": \"hsl(295.4,70.2%,32.9%)\",\n      \"oklch\": \"oklch(0.45,0.19,325)\",\n      \"rgbChannel\": \"134 25 143\",\n      \"hslChannel\": \"295.4 70.2% 32.9%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#701a75\",\n      \"rgb\": \"rgb(112,26,117)\",\n      \"hsl\": \"hsl(296.7,63.6%,28%)\",\n      \"oklch\": \"oklch(0.40,0.16,326)\",\n      \"rgbChannel\": \"112 26 117\",\n      \"hslChannel\": \"296.7 63.6% 28%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#4a044e\",\n      \"rgb\": \"rgb(74,4,78)\",\n      \"hsl\": \"hsl(296.8,90.2%,16.1%)\",\n      \"oklch\": \"oklch(0.29,0.13,326)\",\n      \"rgbChannel\": \"74 4 78\",\n      \"hslChannel\": \"296.8 90.2% 16.1%\"\n    }\n  ],\n  \"pink\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#fdf2f8\",\n      \"rgb\": \"rgb(253,242,248)\",\n      \"hsl\": \"hsl(327.3,73.3%,97.1%)\",\n      \"oklch\": \"oklch(0.97,0.01,343)\",\n      \"rgbChannel\": \"253 242 248\",\n      \"hslChannel\": \"327.3 73.3% 97.1%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#fce7f3\",\n      \"rgb\": \"rgb(252,231,243)\",\n      \"hsl\": \"hsl(325.7,77.8%,94.7%)\",\n      \"oklch\": \"oklch(0.95,0.03,342)\",\n      \"rgbChannel\": \"252 231 243\",\n      \"hslChannel\": \"325.7 77.8% 94.7%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#fbcfe8\",\n      \"rgb\": \"rgb(251,207,232)\",\n      \"hsl\": \"hsl(325.9,84.6%,89.8%)\",\n      \"oklch\": \"oklch(0.90,0.06,343)\",\n      \"rgbChannel\": \"251 207 232\",\n      \"hslChannel\": \"325.9 84.6% 89.8%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#f9a8d4\",\n      \"rgb\": \"rgb(249,168,212)\",\n      \"hsl\": \"hsl(327.4,87.1%,81.8%)\",\n      \"oklch\": \"oklch(0.82,0.11,346)\",\n      \"rgbChannel\": \"249 168 212\",\n      \"hslChannel\": \"327.4 87.1% 81.8%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#f472b6\",\n      \"rgb\": \"rgb(244,114,182)\",\n      \"hsl\": \"hsl(328.6,85.5%,70.2%)\",\n      \"oklch\": \"oklch(0.73,0.18,350)\",\n      \"rgbChannel\": \"244 114 182\",\n      \"hslChannel\": \"328.6 85.5% 70.2%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#ec4899\",\n      \"rgb\": \"rgb(236,72,153)\",\n      \"hsl\": \"hsl(330.4,81.2%,60.4%)\",\n      \"oklch\": \"oklch(0.66,0.21,354)\",\n      \"rgbChannel\": \"236 72 153\",\n      \"hslChannel\": \"330.4 81.2% 60.4%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#db2777\",\n      \"rgb\": \"rgb(219,39,119)\",\n      \"hsl\": \"hsl(333.3,71.4%,50.6%)\",\n      \"oklch\": \"oklch(0.59,0.22,1)\",\n      \"rgbChannel\": \"219 39 119\",\n      \"hslChannel\": \"333.3 71.4% 50.6%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#be185d\",\n      \"rgb\": \"rgb(190,24,93)\",\n      \"hsl\": \"hsl(335.1,77.6%,42%)\",\n      \"oklch\": \"oklch(0.52,0.20,4)\",\n      \"rgbChannel\": \"190 24 93\",\n      \"hslChannel\": \"335.1 77.6% 42%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#9d174d\",\n      \"rgb\": \"rgb(157,23,77)\",\n      \"hsl\": \"hsl(335.8,74.4%,35.3%)\",\n      \"oklch\": \"oklch(0.46,0.17,4)\",\n      \"rgbChannel\": \"157 23 77\",\n      \"hslChannel\": \"335.8 74.4% 35.3%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#831843\",\n      \"rgb\": \"rgb(131,24,67)\",\n      \"hsl\": \"hsl(335.9,69%,30.4%)\",\n      \"oklch\": \"oklch(0.41,0.14,2)\",\n      \"rgbChannel\": \"131 24 67\",\n      \"hslChannel\": \"335.9 69% 30.4%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#500724\",\n      \"rgb\": \"rgb(80,7,36)\",\n      \"hsl\": \"hsl(336.2,83.9%,17.1%)\",\n      \"oklch\": \"oklch(0.28,0.10,4)\",\n      \"rgbChannel\": \"80 7 36\",\n      \"hslChannel\": \"336.2 83.9% 17.1%\"\n    }\n  ],\n  \"rose\": [\n    {\n      \"scale\": 50,\n      \"hex\": \"#fff1f2\",\n      \"rgb\": \"rgb(255,241,242)\",\n      \"hsl\": \"hsl(355.7,100%,97.3%)\",\n      \"oklch\": \"oklch(0.97,0.02,12)\",\n      \"rgbChannel\": \"255 241 242\",\n      \"hslChannel\": \"355.7 100% 97.3%\"\n    },\n    {\n      \"scale\": 100,\n      \"hex\": \"#ffe4e6\",\n      \"rgb\": \"rgb(255,228,230)\",\n      \"hsl\": \"hsl(355.6,100%,94.7%)\",\n      \"oklch\": \"oklch(0.94,0.03,13)\",\n      \"rgbChannel\": \"255 228 230\",\n      \"hslChannel\": \"355.6 100% 94.7%\"\n    },\n    {\n      \"scale\": 200,\n      \"hex\": \"#fecdd3\",\n      \"rgb\": \"rgb(254,205,211)\",\n      \"hsl\": \"hsl(352.7,96.1%,90%)\",\n      \"oklch\": \"oklch(0.89,0.06,10)\",\n      \"rgbChannel\": \"254 205 211\",\n      \"hslChannel\": \"352.7 96.1% 90%\"\n    },\n    {\n      \"scale\": 300,\n      \"hex\": \"#fda4af\",\n      \"rgb\": \"rgb(253,164,175)\",\n      \"hsl\": \"hsl(352.6,95.7%,81.8%)\",\n      \"oklch\": \"oklch(0.81,0.11,12)\",\n      \"rgbChannel\": \"253 164 175\",\n      \"hslChannel\": \"352.6 95.7% 81.8%\"\n    },\n    {\n      \"scale\": 400,\n      \"hex\": \"#fb7185\",\n      \"rgb\": \"rgb(251,113,133)\",\n      \"hsl\": \"hsl(351.3,94.5%,71.4%)\",\n      \"oklch\": \"oklch(0.72,0.17,13)\",\n      \"rgbChannel\": \"251 113 133\",\n      \"hslChannel\": \"351.3 94.5% 71.4%\"\n    },\n    {\n      \"scale\": 500,\n      \"hex\": \"#f43f5e\",\n      \"rgb\": \"rgb(244,63,94)\",\n      \"hsl\": \"hsl(349.7,89.2%,60.2%)\",\n      \"oklch\": \"oklch(0.65,0.22,16)\",\n      \"rgbChannel\": \"244 63 94\",\n      \"hslChannel\": \"349.7 89.2% 60.2%\"\n    },\n    {\n      \"scale\": 600,\n      \"hex\": \"#e11d48\",\n      \"rgb\": \"rgb(225,29,72)\",\n      \"hsl\": \"hsl(346.8,77.2%,49.8%)\",\n      \"oklch\": \"oklch(0.59,0.22,18)\",\n      \"rgbChannel\": \"225 29 72\",\n      \"hslChannel\": \"346.8 77.2% 49.8%\"\n    },\n    {\n      \"scale\": 700,\n      \"hex\": \"#be123c\",\n      \"rgb\": \"rgb(190,18,60)\",\n      \"hsl\": \"hsl(345.3,82.7%,40.8%)\",\n      \"oklch\": \"oklch(0.51,0.20,17)\",\n      \"rgbChannel\": \"190 18 60\",\n      \"hslChannel\": \"345.3 82.7% 40.8%\"\n    },\n    {\n      \"scale\": 800,\n      \"hex\": \"#9f1239\",\n      \"rgb\": \"rgb(159,18,57)\",\n      \"hsl\": \"hsl(343.4,79.7%,34.7%)\",\n      \"oklch\": \"oklch(0.45,0.17,14)\",\n      \"rgbChannel\": \"159 18 57\",\n      \"hslChannel\": \"343.4 79.7% 34.7%\"\n    },\n    {\n      \"scale\": 900,\n      \"hex\": \"#881337\",\n      \"rgb\": \"rgb(136,19,55)\",\n      \"hsl\": \"hsl(341.5,75.5%,30.4%)\",\n      \"oklch\": \"oklch(0.41,0.15,10)\",\n      \"rgbChannel\": \"136 19 55\",\n      \"hslChannel\": \"341.5 75.5% 30.4%\"\n    },\n    {\n      \"scale\": 950,\n      \"hex\": \"#4c0519\",\n      \"rgb\": \"rgb(76,5,25)\",\n      \"hsl\": \"hsl(343.1,87.7%,15.9%)\",\n      \"oklch\": \"oklch(0.27,0.10,12)\",\n      \"rgbChannel\": \"76 5 25\",\n      \"hslChannel\": \"343.1 87.7% 15.9%\"\n    }\n  ]\n}\n"
  },
  {
    "path": "crates/ui/src/theme/default-theme.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Default\",\n  \"author\": \"shadcn\",\n  \"url\": \"https://ui.shadcn.com\",\n  \"_ref\": \"https://github.com/shadcn-ui/ui/blob/main/apps/v4/public/r/colors/neutral.json\",\n  \"themes\": [\n    {\n      \"is_default\": true,\n      \"name\": \"Default Light\",\n      \"mode\": \"light\",\n      \"colors\": {\n        \"accent.background\": \"neutral-100\",\n        \"accent.foreground\": \"neutral-900\",\n        \"accordion.background\": \"white\",\n        \"background\": \"white\",\n        \"border\": \"neutral-200\",\n        \"group_box.background\": \"#f5f5f5\",\n        \"group_box.foreground\": \"#171717\",\n        \"caret\": \"#0a0a0a\",\n        \"chart_1\": \"#93c5fd\",\n        \"chart_2\": \"#3b82f6\",\n        \"chart_3\": \"#2563eb\",\n        \"chart_4\": \"#1d4ed8\",\n        \"chart_5\": \"#1e40af\",\n        \"chart_bullish\": \"green-600\",\n        \"chart_bearish\": \"red-600\",\n        \"danger.background\": \"red-500\",\n        \"danger.foreground\": \"neutral-50\",\n        \"description_list_label.foreground\": \"#171717\",\n        \"drag_border\": \"#3b82f6\",\n        \"drop_target.background\": \"#3b82f640\",\n        \"foreground\": \"neutral-950\",\n        \"info.background\": \"cyan-500\",\n        \"info.foreground\": \"neutral-50\",\n        \"input.border\": \"neutral-200\",\n        \"link.foreground\": \"#0a0a0a\",\n        \"link.active.foreground\": \"#0a0a0a\",\n        \"link.hover.foreground\": \"#404040\",\n        \"list.background\": \"white\",\n        \"list.active.background\": \"#bfdbfe33\",\n        \"list.active.border\": \"#60a5fa\",\n        \"list.even.background\": \"#fafafa\",\n        \"list.head.background\": \"#fafafa\",\n        \"list.hover.background\": \"#f5f5f5\",\n        \"muted.background\": \"neutral-100\",\n        \"muted.foreground\": \"neutral-500\",\n        \"popover.background\": \"white\",\n        \"popover.foreground\": \"neutral-950\",\n        \"primary.background\": \"neutral-900\",\n        \"primary.active.background\": \"neutral-950\",\n        \"primary.foreground\": \"neutral-50\",\n        \"primary.hover.background\": \"neutral-800\",\n        \"progress_bar.background\": \"#171717\",\n        \"ring\": \"neutral-950\",\n        \"scrollbar.background\": \"#fafafa00\",\n        \"scrollbar.thumb.background\": \"#a3a3a3e6\",\n        \"scrollbar.thumb.hover.background\": \"#a3a3a3\",\n        \"secondary.background\": \"neutral-200\",\n        \"secondary.active.background\": \"neutral-300\",\n        \"secondary.foreground\": \"neutral-900\",\n        \"secondary.hover.background\": \"neutral-200\",\n        \"selection.background\": \"#55a0fc\",\n        \"sidebar.background\": \"#fafafa\",\n        \"sidebar.accent.background\": \"#e5e5e5\",\n        \"sidebar.accent.foreground\": \"#171717\",\n        \"sidebar.border\": \"#e5e5e5\",\n        \"sidebar.foreground\": \"#171717\",\n        \"sidebar.primary.background\": \"#171717\",\n        \"sidebar.primary.foreground\": \"#fafafa\",\n        \"skeleton.background\": \"#f5f5f5\",\n        \"slider.bar.background\": \"#171717\",\n        \"slider.thumb.background\": \"white\",\n        \"success.background\": \"green-500\",\n        \"success.foreground\": \"neutral-50\",\n        \"switch.background\": \"#d4d4d4\",\n        \"tab.background\": \"#00000000\",\n        \"tab.active.background\": \"white\",\n        \"tab.active.foreground\": \"#171717\",\n        \"tab_bar.background\": \"#f5f5f5\",\n        \"tab_bar.segmented.background\": \"#f5f5f5\",\n        \"tab.foreground\": \"#404040\",\n        \"table.background\": \"white\",\n        \"table.active.background\": \"#bfdbfe33\",\n        \"table.active.border\": \"#60a5fa\",\n        \"table.even.background\": \"#fafafa\",\n        \"table.head.background\": \"#fafafa\",\n        \"table.head.foreground\": \"#737373\",\n        \"table.hover.background\": \"#f5f5f5\",\n        \"table.row.border\": \"#e5e5e5b3\",\n        \"tiles.background\": \"#fafafa\",\n        \"title_bar.background\": \"#F8F8F8\",\n        \"title_bar.border\": \"#e5e5e5\",\n        \"warning.background\": \"yellow-500\",\n        \"warning.foreground\": \"neutral-50\",\n        \"overlay\": \"#0000000d\",\n        \"window.border\": \"#e5e5e5\",\n        \"base.red\": \"red-600\",\n        \"base.red.light\": \"red-400\",\n        \"base.green\": \"green-600\",\n        \"base.green.light\": \"green-400\",\n        \"base.blue\": \"blue-600\",\n        \"base.blue.light\": \"blue-400\",\n        \"base.yellow\": \"yellow-600\",\n        \"base.yellow.light\": \"yellow-400\",\n        \"base.magenta\": \"purple-600\",\n        \"base.magenta.light\": \"purple-400\",\n        \"base.cyan\": \"cyan-600\",\n        \"base.cyan.light\": \"cyan-400\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#000000\",\n        \"editor.background\": \"#ffffff\",\n        \"editor.active_line.background\": \"#F5F5F5\",\n        \"editor.line_number\": \"#929292\",\n        \"editor.active_line_number\": \"#000000\",\n        \"editor.invisible\": \"#73737366\",\n        \"conflict\": \"#C5060B\",\n        \"created\": \"#1642FF\",\n        \"hidden\": \"#6D6D6D\",\n        \"hint\": \"#9e5dff\",\n        \"modified\": \"#9e7008\",\n        \"predictive\": \"#A4ABB6\",\n        \"warning\": \"#C99401\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#957931\"\n          },\n          \"boolean\": {\n            \"color\": \"#C5060B\"\n          },\n          \"comment\": {\n            \"color\": \"#007fff\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#007fff\"\n          },\n          \"constant\": {\n            \"color\": \"#C5060B\"\n          },\n          \"constructor\": {\n            \"color\": \"#0433ff\"\n          },\n          \"embedded\": {\n            \"color\": \"#333333\"\n          },\n          \"function\": {\n            \"color\": \"#0000A2\"\n          },\n          \"keyword\": {\n            \"color\": \"#0433ff\"\n          },\n          \"link_text\": {\n            \"color\": \"#0000A2\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#6A7293\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#0433ff\"\n          },\n          \"string\": {\n            \"color\": \"#036A07\"\n          },\n          \"string.escape\": {\n            \"color\": \"#036A07\"\n          },\n          \"string.regex\": {\n            \"color\": \"#036A07\"\n          },\n          \"string.special\": {\n            \"color\": \"#d21f07\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#d21f07\"\n          },\n          \"tag\": {\n            \"color\": \"#0433ff\"\n          },\n          \"text.literal\": {\n            \"color\": \"#6F42C1\"\n          },\n          \"title\": {\n            \"color\": \"#0433FF\"\n          },\n          \"type\": {\n            \"color\": \"#6f42c1\"\n          },\n          \"property\": {\n            \"color\": \"#333333\"\n          },\n          \"variable\": {\n            \"color\": \"#333333\"\n          },\n          \"variable.special\": {\n            \"color\": \"#C5060B\"\n          }\n        }\n      }\n    },\n    {\n      \"is_default\": true,\n      \"name\": \"Default Dark\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"neutral-800\",\n        \"accent.foreground\": \"neutral-50\",\n        \"accordion.background\": \"#0a0a0a\",\n        \"background\": \"neutral-950\",\n        \"border\": \"neutral-800\",\n        \"group_box.background\": \"neutral-950\",\n        \"group_box.foreground\": \"neutral-50\",\n        \"caret\": \"#fafafa\",\n        \"chart_1\": \"#93c5fd\",\n        \"chart_2\": \"#3b82f6\",\n        \"chart_3\": \"#2563eb\",\n        \"chart_4\": \"#1d4ed8\",\n        \"chart_5\": \"#1e40af\",\n        \"chart_bullish\": \"green-600\",\n        \"chart_bearish\": \"red-600\",\n        \"danger.background\": \"red-400\",\n        \"danger.foreground\": \"red-600\",\n        \"description_list_label.background\": \"#171717\",\n        \"description_list_label.foreground\": \"#f5f5f5\",\n        \"drag_border\": \"#3b82f6\",\n        \"drop_target.background\": \"#3b82f619\",\n        \"foreground\": \"neutral-50\",\n        \"info.background\": \"cyan-400\",\n        \"info.foreground\": \"cyan-600\",\n        \"input.border\": \"#2f2f2f\",\n        \"link.foreground\": \"#fafafa\",\n        \"link.active.foreground\": \"#d4d4d4\",\n        \"link.hover.foreground\": \"#ffffff\",\n        \"list.background\": \"#0a0a0a\",\n        \"list.active.background\": \"#1e40af33\",\n        \"list.active.border\": \"#1d4ed8\",\n        \"list.even.background\": \"#17171766\",\n        \"list.head.background\": \"#17171766\",\n        \"muted.background\": \"neutral-800\",\n        \"muted.foreground\": \"neutral-400\",\n        \"popover.background\": \"neutral-950\",\n        \"popover.foreground\": \"neutral-50\",\n        \"primary.background\": \"neutral-50\",\n        \"primary.active.background\": \"neutral-200\",\n        \"primary.foreground\": \"neutral-900\",\n        \"primary.hover.background\": \"neutral-100\",\n        \"progress_bar.background\": \"#f5f5f5\",\n        \"ring\": \"neutral-300\",\n        \"scrollbar.background\": \"#17171700\",\n        \"scrollbar.thumb.background\": \"#525252e6\",\n        \"scrollbar.thumb.hover.background\": \"#525252\",\n        \"secondary.background\": \"neutral-800\",\n        \"secondary.active.background\": \"#212121\",\n        \"secondary.foreground\": \"neutral-50\",\n        \"secondary.hover.background\": \"#292929\",\n        \"selection.background\": \"#1d4ed8\",\n        \"sidebar.background\": \"#0a0a0a\",\n        \"sidebar.accent.background\": \"#262626\",\n        \"sidebar.accent.foreground\": \"#f5f5f5\",\n        \"sidebar.border\": \"#262626\",\n        \"sidebar.foreground\": \"#f5f5f5\",\n        \"sidebar.primary.background\": \"#f5f5f5\",\n        \"sidebar.primary.foreground\": \"#0a0a0a\",\n        \"skeleton.background\": \"#171717\",\n        \"slider.bar.background\": \"#fafafa\",\n        \"slider.thumb.background\": \"#0a0a0a\",\n        \"success.background\": \"green-400\",\n        \"success.foreground\": \"green-600\",\n        \"switch.background\": \"#404040\",\n        \"tab.background\": \"#00000000\",\n        \"tab.active.background\": \"#0a0a0a\",\n        \"tab.active.foreground\": \"#fafafa\",\n        \"tab_bar.background\": \"#171717\",\n        \"tab_bar.segmented.background\": \"#171717\",\n        \"tab.foreground\": \"#d4d4d4\",\n        \"table.background\": \"#0a0a0a\",\n        \"table.head.foreground\": \"#525252\",\n        \"table.row.border\": \"#262626b3\",\n        \"tiles.background\": \"#171717\",\n        \"title_bar.background\": \"#171717\",\n        \"title_bar.border\": \"#262626\",\n        \"warning.background\": \"yellow-400\",\n        \"warning.foreground\": \"yellow-600\",\n        \"overlay\": \"#ffffff08\",\n        \"window.border\": \"#262626\",\n        \"base.red\": \"red-400\",\n        \"base.red.light\": \"red-300\",\n        \"base.green\": \"green-400\",\n        \"base.green.light\": \"green-300\",\n        \"base.blue\": \"blue-400\",\n        \"base.blue.light\": \"blue-300\",\n        \"base.yellow\": \"yellow-400\",\n        \"base.yellow.light\": \"yellow-300\",\n        \"base.magenta\": \"purple-400\",\n        \"base.magenta.light\": \"purple-300\",\n        \"base.cyan\": \"cyan-400\",\n        \"base.cyan.light\": \"cyan-300\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#CACCCA\",\n        \"editor.background\": \"#0a0a0a\",\n        \"editor.active_line.background\": \"#171717\",\n        \"editor.line_number\": \"#8F8F8F\",\n        \"editor.active_line_number\": \"#DDDDDD\",\n        \"editor.invisible\": \"#73737366\",\n        \"conflict\": \"#D2602D\",\n        \"created\": \"#3f72e2\",\n        \"created.background\": \"#0C4619\",\n        \"deleted.background\": \"#46190C\",\n        \"error.background\": \"#46190C\",\n        \"error.border\": \"#802207\",\n        \"hidden\": \"#9E9E9E\",\n        \"hint\": \"#b283f8\",\n        \"hint.background\": \"#250c4b\",\n        \"hint.border\": \"#3f0891\",\n        \"info.background\": \"#0059D1\",\n        \"info.border\": \"#0059D1\",\n        \"modified\": \"#B0A878\",\n        \"modified.background\": \"#3A310E\",\n        \"predictive\": \"#5D5945\",\n        \"success.background\": \"#0C4619\",\n        \"warning.background\": \"#3A310E\",\n        \"warning.border\": \"#7B6508\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#e7cb8f\"\n          },\n          \"boolean\": {\n            \"color\": \"#E1D797\"\n          },\n          \"comment\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"constant\": {\n            \"color\": \"#E1D797\"\n          },\n          \"constructor\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"embedded\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"function\": {\n            \"color\": \"#fdd888\"\n          },\n          \"keyword\": {\n            \"color\": \"#c28b12\"\n          },\n          \"link_text\": {\n            \"color\": \"#307BF6\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#7faef9\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string\": {\n            \"color\": \"#62BA46\"\n          },\n          \"string.escape\": {\n            \"color\": \"#62BA46\"\n          },\n          \"string.regex\": {\n            \"color\": \"#62BA46\"\n          },\n          \"string.special\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#E1D797\"\n          },\n          \"tag\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"text.literal\": {\n            \"color\": \"#E1D797\"\n          },\n          \"title\": {\n            \"color\": \"#fdd888\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#c75828\"\n          },\n          \"property\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"variable.special\": {\n            \"color\": \"#E19773\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "crates/ui/src/theme/mod.rs",
    "content": "use crate::{\n    highlighter::HighlightTheme, list::ListSettings, notification::NotificationSettings,\n    scroll::ScrollbarShow, sheet::SheetSettings,\n};\nuse gpui::{App, Global, Hsla, Pixels, SharedString, Window, WindowAppearance, px};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse std::{\n    ops::{Deref, DerefMut},\n    rc::Rc,\n    sync::Arc,\n};\n\nmod color;\nmod registry;\nmod schema;\nmod theme_color;\n\npub use color::*;\npub use registry::*;\npub use schema::*;\npub use theme_color::*;\n\npub fn init(cx: &mut App) {\n    registry::init(cx);\n\n    // Ensure theme is loaded directly on startup for WASM compatibility\n    Theme::change(ThemeMode::Light, None, cx);\n    Theme::sync_scrollbar_appearance(cx);\n}\n\npub trait ActiveTheme {\n    fn theme(&self) -> &Theme;\n}\n\nimpl ActiveTheme for App {\n    #[inline(always)]\n    fn theme(&self) -> &Theme {\n        Theme::global(self)\n    }\n}\n\n/// The global theme configuration.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct Theme {\n    pub colors: ThemeColor,\n    pub highlight_theme: Arc<HighlightTheme>,\n    pub light_theme: Rc<ThemeConfig>,\n    pub dark_theme: Rc<ThemeConfig>,\n\n    pub mode: ThemeMode,\n    /// The font family for the application, default is `.SystemUIFont`.\n    pub font_family: SharedString,\n    /// The base font size for the application, default is 16px.\n    pub font_size: Pixels,\n    /// The monospace font family for the application.\n    ///\n    /// Defaults to:\n    ///\n    /// - macOS: `Menlo`\n    /// - Windows: `Consolas`\n    /// - Linux: `DejaVu Sans Mono`\n    pub mono_font_family: SharedString,\n    /// The monospace font size for the application, default is 13px.\n    pub mono_font_size: Pixels,\n    /// Radius for the general elements.\n    pub radius: Pixels,\n    /// Radius for the large elements, e.g.: Dialog, Notification border radius.\n    pub radius_lg: Pixels,\n    pub shadow: bool,\n    pub transparent: Hsla,\n    /// Show the scrollbar mode, default: Scrolling\n    pub scrollbar_show: ScrollbarShow,\n    /// The notification setting.\n    pub notification: NotificationSettings,\n    /// Tile grid size, default is 4px.\n    pub tile_grid_size: Pixels,\n    /// The shadow of the tile panel.\n    pub tile_shadow: bool,\n    /// The border radius of the tile panel, default is 0px.\n    pub tile_radius: Pixels,\n    /// The list settings.\n    pub list: ListSettings,\n    /// The sheet settings.\n    pub sheet: SheetSettings,\n}\n\nimpl Default for Theme {\n    fn default() -> Self {\n        Self::from(&ThemeColor::default())\n    }\n}\n\nimpl Deref for Theme {\n    type Target = ThemeColor;\n\n    fn deref(&self) -> &Self::Target {\n        &self.colors\n    }\n}\n\nimpl DerefMut for Theme {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.colors\n    }\n}\n\nimpl Global for Theme {}\n\nimpl Theme {\n    /// Returns the global theme reference\n    #[inline(always)]\n    pub fn global(cx: &App) -> &Theme {\n        cx.global::<Theme>()\n    }\n\n    /// Returns the global theme mutable reference\n    #[inline(always)]\n    pub fn global_mut(cx: &mut App) -> &mut Theme {\n        cx.global_mut::<Theme>()\n    }\n\n    /// Returns true if the theme is dark.\n    #[inline(always)]\n    pub fn is_dark(&self) -> bool {\n        self.mode.is_dark()\n    }\n\n    /// Returns the current theme name.\n    pub fn theme_name(&self) -> &SharedString {\n        if self.is_dark() {\n            &self.dark_theme.name\n        } else {\n            &self.light_theme.name\n        }\n    }\n\n    /// Sync the theme with the system appearance\n    pub fn sync_system_appearance(window: Option<&mut Window>, cx: &mut App) {\n        // Better use window.appearance() for avoid error on Linux.\n        // https://github.com/longbridge/gpui-component/issues/104\n        let appearance = window\n            .as_ref()\n            .map(|window| window.appearance())\n            .unwrap_or_else(|| cx.window_appearance());\n\n        Self::change(appearance, window, cx);\n    }\n\n    /// Sync the Scrollbar showing behavior with the system\n    pub fn sync_scrollbar_appearance(cx: &mut App) {\n        Theme::global_mut(cx).scrollbar_show = if cx.should_auto_hide_scrollbars() {\n            ScrollbarShow::Scrolling\n        } else {\n            ScrollbarShow::Hover\n        };\n    }\n\n    /// Change the theme mode.\n    pub fn change(mode: impl Into<ThemeMode>, window: Option<&mut Window>, cx: &mut App) {\n        let mode = mode.into();\n        if !cx.has_global::<Theme>() {\n            let mut theme = Theme::default();\n            theme.light_theme = ThemeRegistry::global(cx).default_light_theme().clone();\n            theme.dark_theme = ThemeRegistry::global(cx).default_dark_theme().clone();\n            cx.set_global(theme);\n        }\n\n        let theme = cx.global_mut::<Theme>();\n        theme.mode = mode;\n        if mode.is_dark() {\n            theme.apply_config(&theme.dark_theme.clone());\n        } else {\n            theme.apply_config(&theme.light_theme.clone());\n        }\n\n        if let Some(window) = window {\n            window.refresh();\n        }\n    }\n\n    /// Get the input background color.\n    ///\n    /// For dark, use a transparent color mixed with the input border: `cx.theme().input`,\n    /// otherwise use the `cx.theme().background` color.\n    #[inline]\n    pub fn input_background(&self) -> Hsla {\n        if self.is_dark() {\n            self.input.mix_oklab(self.transparent, 0.3)\n        } else {\n            self.background\n        }\n    }\n\n    /// Get the editor background color, if not set, use the input background color.\n    #[inline]\n    pub(crate) fn editor_background(&self) -> Hsla {\n        self.highlight_theme\n            .style\n            .editor_background\n            .unwrap_or_else(|| self.input_background())\n    }\n}\n\nimpl From<&ThemeColor> for Theme {\n    fn from(colors: &ThemeColor) -> Self {\n        Theme {\n            mode: ThemeMode::default(),\n            transparent: Hsla::transparent_black(),\n            font_family: \".SystemUIFont\".into(),\n            font_size: px(16.),\n            mono_font_family: if cfg!(target_os = \"macos\") {\n                // https://en.wikipedia.org/wiki/Menlo_(typeface)\n                \"Menlo\".into()\n            } else if cfg!(target_os = \"windows\") {\n                \"Consolas\".into()\n            } else {\n                \"DejaVu Sans Mono\".into()\n            },\n            mono_font_size: px(13.),\n            radius: px(6.),\n            radius_lg: px(8.),\n            shadow: true,\n            scrollbar_show: ScrollbarShow::default(),\n            notification: NotificationSettings::default(),\n            tile_grid_size: px(8.),\n            tile_shadow: true,\n            tile_radius: px(0.),\n            list: ListSettings::default(),\n            colors: *colors,\n            light_theme: Rc::new(ThemeConfig::default()),\n            dark_theme: Rc::new(ThemeConfig::default()),\n            highlight_theme: HighlightTheme::default_light(),\n            sheet: SheetSettings::default(),\n        }\n    }\n}\n\n#[derive(\n    Debug,\n    Clone,\n    Copy,\n    Default,\n    PartialEq,\n    PartialOrd,\n    Eq,\n    Ord,\n    Hash,\n    Serialize,\n    Deserialize,\n    JsonSchema,\n)]\n#[serde(rename_all = \"snake_case\")]\npub enum ThemeMode {\n    #[default]\n    Light,\n    Dark,\n}\n\nimpl ThemeMode {\n    #[inline(always)]\n    pub fn is_dark(&self) -> bool {\n        matches!(self, Self::Dark)\n    }\n\n    /// Return lower_case theme name: `light`, `dark`.\n    pub fn name(&self) -> &'static str {\n        match self {\n            ThemeMode::Light => \"light\",\n            ThemeMode::Dark => \"dark\",\n        }\n    }\n}\n\nimpl From<WindowAppearance> for ThemeMode {\n    fn from(appearance: WindowAppearance) -> Self {\n        match appearance {\n            WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,\n            WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/theme/registry.rs",
    "content": "use crate::{Theme, ThemeColor, ThemeConfig, ThemeMode, ThemeSet, highlighter::HighlightTheme};\n#[allow(unused)]\nuse anyhow::Result;\nuse gpui::{App, Global, SharedString};\nuse std::{\n    collections::HashMap,\n    path::PathBuf,\n    rc::Rc,\n    sync::{Arc, LazyLock},\n};\n\nconst DEFAULT_THEME: &str = include_str!(\"./default-theme.json\");\npub(crate) static DEFAULT_THEME_COLORS: LazyLock<\n    HashMap<ThemeMode, (Arc<ThemeColor>, Arc<HighlightTheme>)>,\n> = LazyLock::new(|| {\n    let mut colors = HashMap::new();\n\n    let themes: Vec<ThemeConfig> = serde_json::from_str::<ThemeSet>(DEFAULT_THEME)\n        .expect(\"Failed to parse themes/default.json\")\n        .themes;\n\n    for theme in themes {\n        let mut theme_color = ThemeColor::default();\n        theme_color.apply_config(&theme, &ThemeColor::default());\n\n        let highlight_theme = HighlightTheme {\n            name: theme.name.to_string(),\n            appearance: theme.mode,\n            style: theme.highlight.unwrap_or_default(),\n        };\n\n        colors.insert(\n            theme.mode,\n            (Arc::new(theme_color), Arc::new(highlight_theme)),\n        );\n    }\n\n    colors\n});\n\npub(super) fn init(cx: &mut App) {\n    cx.set_global(ThemeRegistry::default());\n    ThemeRegistry::global_mut(cx).init_default_themes();\n\n    // Observe changes to the theme registry to apply changes to the active theme\n    cx.observe_global::<ThemeRegistry>(|cx| {\n        let mode = Theme::global(cx).mode;\n        let light_theme = Theme::global(cx).light_theme.name.clone();\n        let dark_theme = Theme::global(cx).dark_theme.name.clone();\n\n        if let Some(theme) = ThemeRegistry::global(cx)\n            .themes()\n            .get(&light_theme)\n            .cloned()\n        {\n            Theme::global_mut(cx).light_theme = theme;\n        }\n        if let Some(theme) = ThemeRegistry::global(cx).themes().get(&dark_theme).cloned() {\n            Theme::global_mut(cx).dark_theme = theme;\n        }\n\n        let theme_name = if mode.is_dark() {\n            dark_theme\n        } else {\n            light_theme\n        };\n\n        tracing::info!(\"Reload active theme: {:?}...\", theme_name);\n        Theme::change(mode, None, cx);\n        cx.refresh_windows();\n    })\n    .detach();\n}\n\n#[derive(Default, Debug)]\npub struct ThemeRegistry {\n    themes_dir: PathBuf,\n    default_themes: HashMap<ThemeMode, Rc<ThemeConfig>>,\n    themes: HashMap<SharedString, Rc<ThemeConfig>>,\n    has_custom_themes: bool,\n}\n\nimpl Global for ThemeRegistry {}\n\nimpl ThemeRegistry {\n    pub fn global(cx: &App) -> &Self {\n        cx.global::<Self>()\n    }\n\n    pub fn global_mut(cx: &mut App) -> &mut Self {\n        cx.global_mut::<Self>()\n    }\n\n    /// Watch themes directory.\n    ///\n    /// And reload themes to trigger the `on_load` callback.\n    #[cfg(not(target_family = \"wasm\"))]\n    pub fn watch_dir<F>(themes_dir: PathBuf, cx: &mut App, on_load: F) -> Result<()>\n    where\n        F: Fn(&mut App) + 'static,\n    {\n        Self::global_mut(cx).themes_dir = themes_dir.clone();\n\n        // Load theme in the background.\n        cx.spawn(async move |cx| {\n            _ = cx.update(|cx| {\n                if let Err(err) = Self::_watch_themes_dir(themes_dir, cx) {\n                    tracing::error!(\"Failed to watch themes directory: {}\", err);\n                }\n\n                Self::reload_themes(cx);\n                on_load(cx);\n            });\n        })\n        .detach();\n\n        Ok(())\n    }\n\n    /// Returns a reference to the map of themes (including default themes).\n    pub fn themes(&self) -> &HashMap<SharedString, Rc<ThemeConfig>> {\n        &self.themes\n    }\n\n    /// Returns a sorted list of themes.\n    pub fn sorted_themes(&self) -> Vec<&Rc<ThemeConfig>> {\n        let mut themes = self.themes.values().collect::<Vec<_>>();\n        // sort by is_default true first, then light first dark later, then by name case-insensitive\n        themes.sort_by(|a, b| {\n            b.is_default\n                .cmp(&a.is_default)\n                .then(a.mode.cmp(&b.mode))\n                .then(a.name.to_lowercase().cmp(&b.name.to_lowercase()))\n        });\n        themes\n    }\n\n    /// Returns a reference to the map of default themes.\n    pub fn default_themes(&self) -> &HashMap<ThemeMode, Rc<ThemeConfig>> {\n        &self.default_themes\n    }\n\n    pub fn default_light_theme(&self) -> &Rc<ThemeConfig> {\n        &self.default_themes[&ThemeMode::Light]\n    }\n\n    pub fn default_dark_theme(&self) -> &Rc<ThemeConfig> {\n        &self.default_themes[&ThemeMode::Dark]\n    }\n\n    pub fn load_themes_from_str(&mut self, content: &str) -> anyhow::Result<()> {\n        let theme_set = serde_json::from_str::<ThemeSet>(content)?;\n        for theme in theme_set.themes {\n            if !self.themes.contains_key(&theme.name) {\n                let theme_name = theme.name.clone();\n                self.themes.insert(theme_name, Rc::new(theme));\n                self.has_custom_themes = true;\n            }\n        }\n        Ok(())\n    }\n\n    fn init_default_themes(&mut self) {\n        let default_themes: Vec<ThemeConfig> = serde_json::from_str::<ThemeSet>(DEFAULT_THEME)\n            .expect(\"failed to parse default theme.\")\n            .themes;\n        for theme in default_themes.into_iter() {\n            if theme.mode.is_dark() {\n                self.default_themes.insert(ThemeMode::Dark, Rc::new(theme));\n            } else {\n                self.default_themes.insert(ThemeMode::Light, Rc::new(theme));\n            }\n        }\n        self.themes_dir = PathBuf::from(\"./themes\");\n        self.themes = self\n            .default_themes\n            .values()\n            .map(|theme| {\n                let name = theme.name.clone();\n                (name, Rc::clone(theme))\n            })\n            .collect();\n    }\n\n    #[cfg(not(target_family = \"wasm\"))]\n    fn _watch_themes_dir(themes_dir: PathBuf, cx: &mut App) -> anyhow::Result<()> {\n        if !themes_dir.exists() {\n            std::fs::create_dir_all(&themes_dir)?;\n        }\n\n        let (tx, rx) = smol::channel::bounded(100);\n        let mut watcher =\n            notify::recommended_watcher(move |res: notify::Result<notify::Event>| {\n                if let Ok(event) = &res {\n                    match event.kind {\n                        notify::EventKind::Create(_)\n                        | notify::EventKind::Modify(_)\n                        | notify::EventKind::Remove(_) => {\n                            if let Err(err) = tx.send_blocking(res) {\n                                tracing::error!(\"Failed to send theme event: {:?}\", err);\n                            }\n                        }\n                        _ => {}\n                    }\n                }\n            })?;\n\n        cx.spawn(async move |cx| {\n            use notify::Watcher as _;\n\n            if let Err(err) = watcher.watch(&themes_dir, notify::RecursiveMode::Recursive) {\n                tracing::error!(\"Failed to watch themes directory: {:?}\", err);\n            }\n\n            while (rx.recv().await).is_ok() {\n                tracing::info!(\"Reloading themes...\");\n                _ = cx.update(Self::reload_themes);\n            }\n        })\n        .detach();\n\n        Ok(())\n    }\n\n    #[cfg(not(target_family = \"wasm\"))]\n    fn reload_themes(cx: &mut App) {\n        let registry = Self::global_mut(cx);\n        match registry.reload() {\n            Ok(_) => {\n                tracing::info!(\"Themes reloaded successfully.\");\n            }\n            Err(e) => tracing::error!(\"Failed to reload themes: {:?}\", e),\n        }\n    }\n\n    #[cfg(not(target_family = \"wasm\"))]\n    /// Reload themes from the `themes_dir`.\n    fn reload(&mut self) -> Result<()> {\n        let mut themes = vec![];\n\n        if self.themes_dir.exists() {\n            for entry in std::fs::read_dir(&self.themes_dir)? {\n                let entry = entry?;\n                let path = entry.path();\n                if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some(\"json\") {\n                    let file_content = std::fs::read_to_string(path.clone())?;\n\n                    match serde_json::from_str::<ThemeSet>(&file_content) {\n                        Ok(theme_set) => {\n                            themes.extend(theme_set.themes);\n                        }\n                        Err(e) => {\n                            tracing::error!(\n                                \"ignored invalid theme file: {}, {}\",\n                                path.display(),\n                                e\n                            );\n                        }\n                    }\n                }\n            }\n        }\n\n        self.themes.clear();\n        for theme in self.default_themes.values() {\n            self.themes\n                .insert(theme.name.clone(), Rc::new((**theme).clone()));\n        }\n\n        for theme in themes.iter() {\n            if self.themes.contains_key(&theme.name) {\n                continue;\n            }\n\n            if theme.is_default {\n                self.default_themes\n                    .insert(theme.mode, Rc::new(theme.clone()));\n            }\n\n            self.has_custom_themes = true;\n            self.themes\n                .insert(theme.name.clone(), Rc::new(theme.clone()));\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/theme/schema.rs",
    "content": "use std::{rc::Rc, sync::Arc};\n\nuse gpui::{SharedString, px};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\nuse crate::{\n    Colorize, Theme, ThemeColor, ThemeMode,\n    highlighter::{HighlightTheme, HighlightThemeStyle},\n    try_parse_color,\n};\n\n/// Represents a theme configuration.\n#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]\n#[serde(default)]\npub struct ThemeSet {\n    /// The name of the theme set.\n    pub name: SharedString,\n    /// The author of the theme.\n    pub author: Option<SharedString>,\n    /// The URL of the theme.\n    pub url: Option<SharedString>,\n    /// The theme list of the theme set.\n    #[serde(rename = \"themes\")]\n    pub themes: Vec<ThemeConfig>,\n}\n\n#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]\n#[serde(default)]\npub struct ThemeConfig {\n    /// Whether this theme is the default theme.\n    pub is_default: bool,\n    /// The name of the theme.\n    pub name: SharedString,\n    /// The mode of the theme, default is light.\n    pub mode: ThemeMode,\n\n    /// The base font size, default is 16.\n    #[serde(rename = \"font.size\")]\n    pub font_size: Option<f32>,\n    /// The base font family, default is system font: `.SystemUIFont`.\n    #[serde(rename = \"font.family\")]\n    pub font_family: Option<SharedString>,\n    /// The monospace font family, default is platform specific:\n    /// - macOS: `Menlo`\n    /// - Windows: `Consolas`\n    /// - Linux: `DejaVu Sans Mono`\n    #[serde(rename = \"mono_font.family\")]\n    pub mono_font_family: Option<SharedString>,\n    /// The monospace font size, default is 13.\n    #[serde(rename = \"mono_font.size\")]\n    pub mono_font_size: Option<f32>,\n\n    /// The border radius for general elements, default is 6.\n    #[serde(rename = \"radius\")]\n    pub radius: Option<usize>,\n    /// The border radius for large elements like Dialogs and Notifications, default is 8.\n    #[serde(rename = \"radius.lg\")]\n    pub radius_lg: Option<usize>,\n    /// Set shadows in the theme, for example the Input and Button, default is true.\n    #[serde(rename = \"shadow\")]\n    pub shadow: Option<bool>,\n\n    /// The colors of the theme.\n    pub colors: ThemeConfigColors,\n    /// The highlight theme, this part is combilbility with `style` section in Zed theme.\n    ///\n    /// https://github.com/zed-industries/zed/blob/f50041779dcfd7a76c8aec293361c60c53f02d51/assets/themes/ayu/ayu.json#L9\n    pub highlight: Option<HighlightThemeStyle>,\n}\n\n#[derive(Debug, Default, Clone, JsonSchema, Serialize, Deserialize)]\npub struct ThemeConfigColors {\n    /// Used for accents such as hover background on MenuItem, ListItem, etc.\n    #[serde(rename = \"accent.background\")]\n    pub accent: Option<SharedString>,\n    /// Used for accent text color.\n    #[serde(rename = \"accent.foreground\")]\n    pub accent_foreground: Option<SharedString>,\n    /// Accordion background color.\n    #[serde(rename = \"accordion.background\")]\n    pub accordion: Option<SharedString>,\n    /// Accordion hover background color.\n    #[serde(rename = \"accordion.hover.background\")]\n    pub accordion_hover: Option<SharedString>,\n    /// Default background color.\n    #[serde(rename = \"background\")]\n    pub background: Option<SharedString>,\n    /// Default border color\n    #[serde(rename = \"border\")]\n    pub border: Option<SharedString>,\n    /// Button primary background color, fallback to `primary`.\n    #[serde(rename = \"button.primary.background\")]\n    pub button_primary: Option<SharedString>,\n    /// Button primary active background color, fallback to `primary_active`.\n    #[serde(rename = \"button.primary.active.background\")]\n    pub button_primary_active: Option<SharedString>,\n    /// Button primary text color, fallback to `primary_foreground`.\n    #[serde(rename = \"button.primary.foreground\")]\n    pub button_primary_foreground: Option<SharedString>,\n    /// Button primary hover background color, fallback to `primary_hover`.\n    #[serde(rename = \"button.primary.hover.background\")]\n    pub button_primary_hover: Option<SharedString>,\n    /// Background color for GroupBox.\n    #[serde(rename = \"group_box.background\")]\n    pub group_box: Option<SharedString>,\n    /// Text color for GroupBox.\n    #[serde(rename = \"group_box.foreground\")]\n    pub group_box_foreground: Option<SharedString>,\n    /// Title text color for GroupBox.\n    #[serde(rename = \"group_box.title.foreground\")]\n    pub group_box_title_foreground: Option<SharedString>,\n    /// Input caret color (Blinking cursor).\n    #[serde(rename = \"caret\")]\n    pub caret: Option<SharedString>,\n    /// Chart 1 color.\n    #[serde(rename = \"chart.1\")]\n    pub chart_1: Option<SharedString>,\n    /// Chart 2 color.\n    #[serde(rename = \"chart.2\")]\n    pub chart_2: Option<SharedString>,\n    /// Chart 3 color.\n    #[serde(rename = \"chart.3\")]\n    pub chart_3: Option<SharedString>,\n    /// Chart 4 color.\n    #[serde(rename = \"chart.4\")]\n    pub chart_4: Option<SharedString>,\n    /// Chart 5 color.\n    #[serde(rename = \"chart.5\")]\n    pub chart_5: Option<SharedString>,\n    /// Bullish color for candlestick charts (upward price movement).\n    #[serde(rename = \"chart_bullish\")]\n    pub chart_bullish: Option<SharedString>,\n    /// Bearish color for candlestick charts (downward price movement).\n    #[serde(rename = \"chart_bearish\")]\n    pub chart_bearish: Option<SharedString>,\n    /// Danger background color.\n    #[serde(rename = \"danger.background\")]\n    pub danger: Option<SharedString>,\n    /// Danger active background color.\n    #[serde(rename = \"danger.active.background\")]\n    pub danger_active: Option<SharedString>,\n    /// Danger text color.\n    #[serde(rename = \"danger.foreground\")]\n    pub danger_foreground: Option<SharedString>,\n    /// Danger hover background color.\n    #[serde(rename = \"danger.hover.background\")]\n    pub danger_hover: Option<SharedString>,\n    /// Description List label background color.\n    #[serde(rename = \"description_list.label.background\")]\n    pub description_list_label: Option<SharedString>,\n    /// Description List label foreground color.\n    #[serde(rename = \"description_list.label.foreground\")]\n    pub description_list_label_foreground: Option<SharedString>,\n    /// Drag border color.\n    #[serde(rename = \"drag.border\")]\n    pub drag_border: Option<SharedString>,\n    /// Drop target background color.\n    #[serde(rename = \"drop_target.background\")]\n    pub drop_target: Option<SharedString>,\n    /// Default text color.\n    #[serde(rename = \"foreground\")]\n    pub foreground: Option<SharedString>,\n    /// Info background color.\n    #[serde(rename = \"info.background\")]\n    pub info: Option<SharedString>,\n    /// Info active background color.\n    #[serde(rename = \"info.active.background\")]\n    pub info_active: Option<SharedString>,\n    /// Info text color.\n    #[serde(rename = \"info.foreground\")]\n    pub info_foreground: Option<SharedString>,\n    /// Info hover background color.\n    #[serde(rename = \"info.hover.background\")]\n    pub info_hover: Option<SharedString>,\n    /// Border color for inputs such as Input, Select, etc.\n    #[serde(rename = \"input.border\")]\n    pub input: Option<SharedString>,\n    /// Link text color.\n    #[serde(rename = \"link\")]\n    pub link: Option<SharedString>,\n    /// Active link text color.\n    #[serde(rename = \"link.active\")]\n    pub link_active: Option<SharedString>,\n    /// Hover link text color.\n    #[serde(rename = \"link.hover\")]\n    pub link_hover: Option<SharedString>,\n    /// Background color for List and ListItem.\n    #[serde(rename = \"list.background\")]\n    pub list: Option<SharedString>,\n    /// Background color for active ListItem.\n    #[serde(rename = \"list.active.background\")]\n    pub list_active: Option<SharedString>,\n    /// Border color for active ListItem.\n    #[serde(rename = \"list.active.border\")]\n    pub list_active_border: Option<SharedString>,\n    /// Stripe background color for even ListItem.\n    #[serde(rename = \"list.even.background\")]\n    pub list_even: Option<SharedString>,\n    /// Background color for List header.\n    #[serde(rename = \"list.head.background\")]\n    pub list_head: Option<SharedString>,\n    /// Hover background color for ListItem.\n    #[serde(rename = \"list.hover.background\")]\n    pub list_hover: Option<SharedString>,\n    /// Muted backgrounds such as Skeleton and Switch.\n    #[serde(rename = \"muted.background\")]\n    pub muted: Option<SharedString>,\n    /// Muted text color, as used in disabled text.\n    #[serde(rename = \"muted.foreground\")]\n    pub muted_foreground: Option<SharedString>,\n    /// Background color for Popover.\n    #[serde(rename = \"popover.background\")]\n    pub popover: Option<SharedString>,\n    /// Text color for Popover.\n    #[serde(rename = \"popover.foreground\")]\n    pub popover_foreground: Option<SharedString>,\n    /// Primary background color.\n    #[serde(rename = \"primary.background\")]\n    pub primary: Option<SharedString>,\n    /// Active primary background color.\n    #[serde(rename = \"primary.active.background\")]\n    pub primary_active: Option<SharedString>,\n    /// Primary text color.\n    #[serde(rename = \"primary.foreground\")]\n    pub primary_foreground: Option<SharedString>,\n    /// Hover primary background color.\n    #[serde(rename = \"primary.hover.background\")]\n    pub primary_hover: Option<SharedString>,\n    /// Progress bar background color.\n    #[serde(rename = \"progress.bar.background\")]\n    pub progress_bar: Option<SharedString>,\n    /// Used for focus ring.\n    #[serde(rename = \"ring\")]\n    pub ring: Option<SharedString>,\n    /// Scrollbar background color.\n    #[serde(rename = \"scrollbar.background\")]\n    pub scrollbar: Option<SharedString>,\n    /// Scrollbar thumb background color.\n    #[serde(rename = \"scrollbar.thumb.background\")]\n    pub scrollbar_thumb: Option<SharedString>,\n    /// Scrollbar thumb hover background color.\n    #[serde(rename = \"scrollbar.thumb.hover.background\")]\n    pub scrollbar_thumb_hover: Option<SharedString>,\n    /// Secondary background color.\n    #[serde(rename = \"secondary.background\")]\n    pub secondary: Option<SharedString>,\n    /// Active secondary background color.\n    #[serde(rename = \"secondary.active.background\")]\n    pub secondary_active: Option<SharedString>,\n    /// Secondary text color, used for secondary Button text color or secondary text.\n    #[serde(rename = \"secondary.foreground\")]\n    pub secondary_foreground: Option<SharedString>,\n    /// Hover secondary background color.\n    #[serde(rename = \"secondary.hover.background\")]\n    pub secondary_hover: Option<SharedString>,\n    /// Input selection background color.\n    #[serde(rename = \"selection.background\")]\n    pub selection: Option<SharedString>,\n    /// Sidebar background color.\n    #[serde(rename = \"sidebar.background\")]\n    pub sidebar: Option<SharedString>,\n    /// Sidebar accent background color.\n    #[serde(rename = \"sidebar.accent.background\")]\n    pub sidebar_accent: Option<SharedString>,\n    /// Sidebar accent text color.\n    #[serde(rename = \"sidebar.accent.foreground\")]\n    pub sidebar_accent_foreground: Option<SharedString>,\n    /// Sidebar border color.\n    #[serde(rename = \"sidebar.border\")]\n    pub sidebar_border: Option<SharedString>,\n    /// Sidebar text color.\n    #[serde(rename = \"sidebar.foreground\")]\n    pub sidebar_foreground: Option<SharedString>,\n    /// Sidebar primary background color.\n    #[serde(rename = \"sidebar.primary.background\")]\n    pub sidebar_primary: Option<SharedString>,\n    /// Sidebar primary text color.\n    #[serde(rename = \"sidebar.primary.foreground\")]\n    pub sidebar_primary_foreground: Option<SharedString>,\n    /// Skeleton background color.\n    #[serde(rename = \"skeleton.background\")]\n    pub skeleton: Option<SharedString>,\n    /// Slider bar background color.\n    #[serde(rename = \"slider.background\")]\n    pub slider_bar: Option<SharedString>,\n    /// Slider thumb background color.\n    #[serde(rename = \"slider.thumb.background\")]\n    pub slider_thumb: Option<SharedString>,\n    /// Success background color.\n    #[serde(rename = \"success.background\")]\n    pub success: Option<SharedString>,\n    /// Success text color.\n    #[serde(rename = \"success.foreground\")]\n    pub success_foreground: Option<SharedString>,\n    /// Success hover background color.\n    #[serde(rename = \"success.hover.background\")]\n    pub success_hover: Option<SharedString>,\n    /// Success active background color.\n    #[serde(rename = \"success.active.background\")]\n    pub success_active: Option<SharedString>,\n    /// Switch background color.\n    #[serde(rename = \"switch.background\")]\n    pub switch: Option<SharedString>,\n    /// Switch thumb background color.\n    #[serde(rename = \"switch.thumb.background\")]\n    pub switch_thumb: Option<SharedString>,\n    /// Tab background color.\n    #[serde(rename = \"tab.background\")]\n    pub tab: Option<SharedString>,\n    /// Tab active background color.\n    #[serde(rename = \"tab.active.background\")]\n    pub tab_active: Option<SharedString>,\n    /// Tab active text color.\n    #[serde(rename = \"tab.active.foreground\")]\n    pub tab_active_foreground: Option<SharedString>,\n    /// TabBar background color.\n    #[serde(rename = \"tab_bar.background\")]\n    pub tab_bar: Option<SharedString>,\n    /// TabBar segmented background color.\n    #[serde(rename = \"tab_bar.segmented.background\")]\n    pub tab_bar_segmented: Option<SharedString>,\n    /// Tab text color.\n    #[serde(rename = \"tab.foreground\")]\n    pub tab_foreground: Option<SharedString>,\n    /// Table background color.\n    #[serde(rename = \"table.background\")]\n    pub table: Option<SharedString>,\n    /// Table active item background color.\n    #[serde(rename = \"table.active.background\")]\n    pub table_active: Option<SharedString>,\n    /// Table active item border color.\n    #[serde(rename = \"table.active.border\")]\n    pub table_active_border: Option<SharedString>,\n    /// Stripe background color for even TableRow.\n    #[serde(rename = \"table.even.background\")]\n    pub table_even: Option<SharedString>,\n    /// Table header background color.\n    #[serde(rename = \"table.head.background\")]\n    pub table_head: Option<SharedString>,\n    /// Table header text color.\n    #[serde(rename = \"table.head.foreground\")]\n    pub table_head_foreground: Option<SharedString>,\n    /// Table footer background color.\n    #[serde(rename = \"table.foot.background\")]\n    pub table_foot: Option<SharedString>,\n    /// Table footer text color.\n    #[serde(rename = \"table.foot.foreground\")]\n    pub table_foot_foreground: Option<SharedString>,\n    /// Table item hover background color.\n    #[serde(rename = \"table.hover.background\")]\n    pub table_hover: Option<SharedString>,\n    /// Table row border color.\n    #[serde(rename = \"table.row.border\")]\n    pub table_row_border: Option<SharedString>,\n    /// TitleBar background color, use for Window title bar.\n    #[serde(rename = \"title_bar.background\")]\n    pub title_bar: Option<SharedString>,\n    /// TitleBar border color.\n    #[serde(rename = \"title_bar.border\")]\n    pub title_bar_border: Option<SharedString>,\n    /// Background color for Tiles.\n    #[serde(rename = \"tiles.background\")]\n    pub tiles: Option<SharedString>,\n    /// Warning background color.\n    #[serde(rename = \"warning.background\")]\n    pub warning: Option<SharedString>,\n    /// Warning active background color.\n    #[serde(rename = \"warning.active.background\")]\n    pub warning_active: Option<SharedString>,\n    /// Warning hover background color.\n    #[serde(rename = \"warning.hover.background\")]\n    pub warning_hover: Option<SharedString>,\n    /// Warning foreground color.\n    #[serde(rename = \"warning.foreground\")]\n    pub warning_foreground: Option<SharedString>,\n    /// Overlay background color.\n    #[serde(rename = \"overlay\")]\n    pub overlay: Option<SharedString>,\n    /// Window border color.\n    ///\n    /// # Platform specific:\n    ///\n    /// This is only works on Linux, other platforms we can't change the window border color.\n    #[serde(rename = \"window.border\")]\n    pub window_border: Option<SharedString>,\n\n    /// Base blue color.\n    #[serde(rename = \"base.blue\")]\n    blue: Option<String>,\n    /// Base light blue color.\n    #[serde(rename = \"base.blue.light\")]\n    blue_light: Option<String>,\n    /// Base cyan color.\n    #[serde(rename = \"base.cyan\")]\n    cyan: Option<String>,\n    /// Base light cyan color.\n    #[serde(rename = \"base.cyan.light\")]\n    cyan_light: Option<String>,\n    /// Base green color.\n    #[serde(rename = \"base.green\")]\n    green: Option<String>,\n    /// Base light green color.\n    #[serde(rename = \"base.green.light\")]\n    green_light: Option<String>,\n    /// Base magenta color.\n    #[serde(rename = \"base.magenta\")]\n    magenta: Option<String>,\n    #[serde(rename = \"base.magenta.light\")]\n    magenta_light: Option<String>,\n    /// Base red color.\n    #[serde(rename = \"base.red\")]\n    red: Option<String>,\n    /// Base light red color.\n    #[serde(rename = \"base.red.light\")]\n    red_light: Option<String>,\n    /// Base yellow color.\n    #[serde(rename = \"base.yellow\")]\n    yellow: Option<String>,\n    /// Base light yellow color.\n    #[serde(rename = \"base.yellow.light\")]\n    yellow_light: Option<String>,\n}\n\nimpl ThemeColor {\n    /// Create a new `ThemeColor` from a `ThemeConfig`.\n    pub(crate) fn apply_config(&mut self, config: &ThemeConfig, default_theme: &ThemeColor) {\n        let colors = config.colors.clone();\n\n        macro_rules! apply_color {\n            ($config_field:ident) => {\n                if let Some(value) = colors.$config_field {\n                    if let Ok(color) = try_parse_color(&value) {\n                        self.$config_field = color;\n                    } else {\n                        self.$config_field = default_theme.$config_field;\n                    }\n                } else {\n                    self.$config_field = default_theme.$config_field;\n                }\n            };\n            // With fallback\n            ($config_field:ident, fallback = $fallback:expr) => {\n                if let Some(value) = colors.$config_field {\n                    if let Ok(color) = try_parse_color(&value) {\n                        self.$config_field = color;\n                    }\n                } else {\n                    self.$config_field = $fallback;\n                }\n            };\n        }\n\n        apply_color!(background);\n\n        // Base colors for fallback\n        apply_color!(red);\n        apply_color!(\n            red_light,\n            fallback = self.background.blend(self.red.opacity(0.8))\n        );\n        apply_color!(green);\n        apply_color!(\n            green_light,\n            fallback = self.background.blend(self.green.opacity(0.8))\n        );\n        apply_color!(blue);\n        apply_color!(\n            blue_light,\n            fallback = self.background.blend(self.blue.opacity(0.8))\n        );\n        apply_color!(magenta);\n        apply_color!(\n            magenta_light,\n            fallback = self.background.blend(self.magenta.opacity(0.8))\n        );\n        apply_color!(yellow);\n        apply_color!(\n            yellow_light,\n            fallback = self.background.blend(self.yellow.opacity(0.8))\n        );\n        apply_color!(cyan);\n        apply_color!(\n            cyan_light,\n            fallback = self.background.blend(self.cyan.opacity(0.8))\n        );\n\n        apply_color!(border);\n        apply_color!(foreground);\n        apply_color!(muted);\n        apply_color!(\n            muted_foreground,\n            fallback = self.muted.blend(self.foreground.opacity(0.7))\n        );\n\n        // Button colors\n        let active_darken = if config.mode.is_dark() { 0.2 } else { 0.1 };\n        let hover_opacity = 0.9;\n        apply_color!(primary);\n        apply_color!(primary_foreground, fallback = self.foreground);\n        apply_color!(\n            primary_hover,\n            fallback = self.background.blend(self.primary.opacity(hover_opacity))\n        );\n        apply_color!(\n            primary_active,\n            fallback = self.primary.darken(active_darken)\n        );\n        apply_color!(button_primary, fallback = self.primary);\n        apply_color!(\n            button_primary_foreground,\n            fallback = self.primary_foreground\n        );\n        apply_color!(button_primary_hover, fallback = self.primary_hover);\n        apply_color!(button_primary_active, fallback = self.primary_active);\n        apply_color!(secondary);\n        apply_color!(secondary_foreground, fallback = self.foreground);\n        apply_color!(\n            secondary_hover,\n            fallback = self.background.blend(self.secondary.opacity(hover_opacity))\n        );\n        apply_color!(\n            secondary_active,\n            fallback = self.secondary.darken(active_darken)\n        );\n        apply_color!(success, fallback = self.green);\n        apply_color!(success_foreground, fallback = self.primary_foreground);\n        apply_color!(\n            success_hover,\n            fallback = self.background.blend(self.success.opacity(hover_opacity))\n        );\n        apply_color!(\n            success_active,\n            fallback = self.success.darken(active_darken)\n        );\n        apply_color!(info, fallback = self.cyan);\n        apply_color!(info_foreground, fallback = self.primary_foreground);\n        apply_color!(\n            info_hover,\n            fallback = self.background.blend(self.info.opacity(hover_opacity))\n        );\n        apply_color!(info_active, fallback = self.info.darken(active_darken));\n        apply_color!(warning, fallback = self.yellow);\n        apply_color!(warning_foreground, fallback = self.primary_foreground);\n        apply_color!(\n            warning_hover,\n            fallback = self.background.blend(self.warning.opacity(0.9))\n        );\n        apply_color!(\n            warning_active,\n            fallback = self.background.blend(self.warning.darken(active_darken))\n        );\n\n        // Other colors\n        apply_color!(accent, fallback = self.secondary);\n        apply_color!(accent_foreground, fallback = self.foreground);\n        apply_color!(accordion, fallback = self.background);\n        apply_color!(accordion_hover, fallback = self.accent.opacity(0.8));\n        apply_color!(\n            group_box,\n            fallback = self\n                .background\n                .blend(\n                    self.secondary\n                        .opacity(if config.mode.is_dark() { 0.3 } else { 0.4 })\n                )\n        );\n        apply_color!(group_box_foreground, fallback = self.foreground);\n        apply_color!(caret, fallback = self.primary);\n        apply_color!(chart_1, fallback = self.blue.lighten(0.4));\n        apply_color!(chart_2, fallback = self.blue.lighten(0.2));\n        apply_color!(chart_3, fallback = self.blue);\n        apply_color!(chart_4, fallback = self.blue.darken(0.2));\n        apply_color!(chart_5, fallback = self.blue.darken(0.4));\n        apply_color!(chart_bullish, fallback = self.green);\n        apply_color!(chart_bearish, fallback = self.red);\n        apply_color!(danger, fallback = self.red);\n        apply_color!(danger_active, fallback = self.danger.darken(active_darken));\n        apply_color!(danger_foreground, fallback = self.primary_foreground);\n        apply_color!(\n            danger_hover,\n            fallback = self.background.blend(self.danger.opacity(0.9))\n        );\n        apply_color!(\n            description_list_label,\n            fallback = self.background.blend(self.border.opacity(0.2))\n        );\n        apply_color!(\n            description_list_label_foreground,\n            fallback = self.muted_foreground\n        );\n        apply_color!(drag_border, fallback = self.primary.opacity(0.65));\n        apply_color!(drop_target, fallback = self.primary.opacity(0.2));\n        apply_color!(input, fallback = self.border);\n        apply_color!(link, fallback = self.primary);\n        apply_color!(link_active, fallback = self.link);\n        apply_color!(link_hover, fallback = self.link);\n        apply_color!(list, fallback = self.background);\n        apply_color!(\n            list_active,\n            fallback = self.background.blend(self.primary.opacity(0.1))\n        );\n        apply_color!(\n            list_active_border,\n            fallback = self.background.blend(self.primary.opacity(0.6))\n        );\n        apply_color!(list_even, fallback = self.list);\n        apply_color!(list_head, fallback = self.list);\n        apply_color!(list_hover, fallback = self.accent.opacity(0.6));\n        apply_color!(popover, fallback = self.background);\n        apply_color!(popover_foreground, fallback = self.foreground);\n        apply_color!(progress_bar, fallback = self.primary);\n        apply_color!(ring, fallback = self.blue);\n        apply_color!(scrollbar, fallback = self.background);\n        apply_color!(scrollbar_thumb, fallback = self.accent);\n        apply_color!(scrollbar_thumb_hover, fallback = self.scrollbar_thumb);\n        apply_color!(selection, fallback = self.primary);\n        apply_color!(\n            sidebar,\n            fallback = self.background.blend(self.border.opacity(0.15))\n        );\n        apply_color!(sidebar_accent, fallback = self.accent);\n        apply_color!(sidebar_accent_foreground, fallback = self.accent_foreground);\n        apply_color!(sidebar_border, fallback = self.border);\n        apply_color!(sidebar_foreground, fallback = self.foreground);\n        apply_color!(sidebar_primary, fallback = self.primary);\n        apply_color!(\n            sidebar_primary_foreground,\n            fallback = self.primary_foreground\n        );\n        apply_color!(skeleton, fallback = self.secondary);\n        apply_color!(slider_bar, fallback = self.primary);\n        apply_color!(slider_thumb, fallback = self.primary_foreground);\n        apply_color!(switch, fallback = self.secondary_active);\n        apply_color!(switch_thumb, fallback = self.background);\n        apply_color!(tab, fallback = self.background);\n        apply_color!(tab_active, fallback = self.background);\n        apply_color!(tab_active_foreground, fallback = self.foreground);\n        apply_color!(tab_bar, fallback = self.background);\n        apply_color!(tab_bar_segmented, fallback = self.secondary);\n        apply_color!(tab_foreground, fallback = self.foreground);\n        apply_color!(table, fallback = self.list);\n        apply_color!(table_active, fallback = self.list_active);\n        apply_color!(table_active_border, fallback = self.list_active_border);\n        apply_color!(table_even, fallback = self.list_even);\n        apply_color!(table_head, fallback = self.list_head);\n        apply_color!(table_head_foreground, fallback = self.muted_foreground);\n        apply_color!(table_foot, fallback = self.list_head);\n        apply_color!(table_foot_foreground, fallback = self.muted_foreground);\n        apply_color!(table_hover, fallback = self.list_hover);\n        apply_color!(table_row_border, fallback = self.border);\n        apply_color!(title_bar, fallback = self.background);\n        apply_color!(title_bar_border, fallback = self.border);\n        apply_color!(tiles, fallback = self.background);\n        apply_color!(overlay);\n        apply_color!(window_border, fallback = self.border);\n\n        // TODO: Apply default fallback colors to highlight.\n\n        // Ensure opacity for list_active, table_active\n        self.list_active = self.list_active.alpha(self.list_active.a.min(0.2));\n        self.table_active = self.table_active.alpha(self.table_active.a.min(0.2));\n        self.selection = self.selection.alpha(self.selection.a.min(0.3));\n    }\n}\n\nimpl Theme {\n    /// Apply the given theme configuration to the current theme.\n    pub fn apply_config(&mut self, config: &Rc<ThemeConfig>) {\n        if config.mode.is_dark() {\n            self.dark_theme = config.clone();\n        } else {\n            self.light_theme = config.clone();\n        }\n        if let Some(style) = &config.highlight {\n            let highlight_theme = Arc::new(HighlightTheme {\n                name: config.name.to_string(),\n                appearance: config.mode,\n                style: style.clone(),\n            });\n            self.highlight_theme = highlight_theme.clone();\n        }\n\n        let default_theme = if config.mode.is_dark() {\n            Self::from(ThemeColor::dark().as_ref())\n        } else {\n            Self::from(ThemeColor::light().as_ref())\n        };\n\n        if let Some(font_size) = config.font_size {\n            self.font_size = px(font_size);\n        } else {\n            self.font_size = default_theme.font_size;\n        }\n        if let Some(font_family) = &config.font_family {\n            self.font_family = font_family.clone();\n        } else {\n            self.font_family = default_theme.font_family.clone();\n        }\n        if let Some(mono_font_family) = &config.mono_font_family {\n            self.mono_font_family = mono_font_family.clone();\n        } else {\n            self.mono_font_family = default_theme.mono_font_family.clone();\n        }\n        if let Some(mono_font_size) = config.mono_font_size {\n            self.mono_font_size = px(mono_font_size);\n        } else {\n            self.mono_font_size = default_theme.mono_font_size;\n        }\n        if let Some(radius) = config.radius {\n            self.radius = px(radius as f32);\n        } else {\n            self.radius = default_theme.radius;\n        }\n        if let Some(radius_lg) = config.radius_lg {\n            self.radius_lg = px(radius_lg as f32);\n        } else {\n            self.radius_lg = default_theme.radius_lg;\n        }\n        if let Some(shadow) = config.shadow {\n            self.shadow = shadow;\n        } else {\n            self.shadow = default_theme.shadow;\n        }\n\n        self.colors.apply_config(&config, &default_theme.colors);\n        self.mode = config.mode;\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/theme/theme_color.rs",
    "content": "use std::sync::Arc;\n\nuse crate::{ThemeMode, theme::DEFAULT_THEME_COLORS};\n\nuse gpui::Hsla;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\n/// Theme colors used throughout the UI components.\n#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema)]\npub struct ThemeColor {\n    /// Used for accents such as hover background on MenuItem, ListItem, etc.\n    pub accent: Hsla,\n    /// Used for accent text color.\n    pub accent_foreground: Hsla,\n    /// Accordion background color.\n    pub accordion: Hsla,\n    /// Accordion hover background color.\n    pub accordion_hover: Hsla,\n    /// Default background color.\n    pub background: Hsla,\n    /// Default border color\n    pub border: Hsla,\n    /// Button primary background color, fallback to `primary`.\n    pub button_primary: Hsla,\n    /// Button primary active background color, fallback to `primary_active`.\n    pub button_primary_active: Hsla,\n    /// Button primary text color, fallback to `primary_foreground`.\n    pub button_primary_foreground: Hsla,\n    /// Button primary hover background color, fallback to `primary_hover`.\n    pub button_primary_hover: Hsla,\n    /// Background color for GroupBox.\n    pub group_box: Hsla,\n    /// Text color for GroupBox.\n    pub group_box_foreground: Hsla,\n    /// Input caret color (Blinking cursor).\n    pub caret: Hsla,\n    /// Chart 1 color.\n    pub chart_1: Hsla,\n    /// Chart 2 color.\n    pub chart_2: Hsla,\n    /// Chart 3 color.\n    pub chart_3: Hsla,\n    /// Chart 4 color.\n    pub chart_4: Hsla,\n    /// Chart 5 color.\n    pub chart_5: Hsla,\n    /// Bullish color for candlestick charts (upward price movement).\n    pub chart_bullish: Hsla,\n    /// Bearish color for candlestick charts (downward price movement).\n    pub chart_bearish: Hsla,\n    /// Danger background color.\n    pub danger: Hsla,\n    /// Danger active background color.\n    pub danger_active: Hsla,\n    /// Danger text color.\n    pub danger_foreground: Hsla,\n    /// Danger hover background color.\n    pub danger_hover: Hsla,\n    /// Description List label background color.\n    pub description_list_label: Hsla,\n    /// Description List label foreground color.\n    pub description_list_label_foreground: Hsla,\n    /// Drag border color.\n    pub drag_border: Hsla,\n    /// Drop target background color.\n    pub drop_target: Hsla,\n    /// Default text color.\n    pub foreground: Hsla,\n    /// Info background color.\n    pub info: Hsla,\n    /// Info active background color.\n    pub info_active: Hsla,\n    /// Info text color.\n    pub info_foreground: Hsla,\n    /// Info hover background color.\n    pub info_hover: Hsla,\n    /// Border color for inputs such as Input, Select, etc.\n    pub input: Hsla,\n    /// Link text color.\n    pub link: Hsla,\n    /// Active link text color.\n    pub link_active: Hsla,\n    /// Hover link text color.\n    pub link_hover: Hsla,\n    /// Background color for List and ListItem.\n    pub list: Hsla,\n    /// Background color for active ListItem.\n    pub list_active: Hsla,\n    /// Border color for active ListItem.\n    pub list_active_border: Hsla,\n    /// Stripe background color for even ListItem.\n    pub list_even: Hsla,\n    /// Background color for List header.\n    pub list_head: Hsla,\n    /// Hover background color for ListItem.\n    pub list_hover: Hsla,\n    /// Muted backgrounds such as Skeleton and Switch.\n    pub muted: Hsla,\n    /// Muted text color, as used in disabled text.\n    pub muted_foreground: Hsla,\n    /// Background color for Popover.\n    pub popover: Hsla,\n    /// Text color for Popover.\n    pub popover_foreground: Hsla,\n    /// Primary background color.\n    pub primary: Hsla,\n    /// Active primary background color.\n    pub primary_active: Hsla,\n    /// Primary text color.\n    pub primary_foreground: Hsla,\n    /// Hover primary background color.\n    pub primary_hover: Hsla,\n    /// Progress bar background color.\n    pub progress_bar: Hsla,\n    /// Used for focus ring.\n    pub ring: Hsla,\n    /// Scrollbar background color.\n    pub scrollbar: Hsla,\n    /// Scrollbar thumb background color.\n    pub scrollbar_thumb: Hsla,\n    /// Scrollbar thumb hover background color.\n    pub scrollbar_thumb_hover: Hsla,\n    /// Secondary background color.\n    pub secondary: Hsla,\n    /// Active secondary background color.\n    pub secondary_active: Hsla,\n    /// Secondary text color, used for secondary Button text color or secondary text.\n    pub secondary_foreground: Hsla,\n    /// Hover secondary background color.\n    pub secondary_hover: Hsla,\n    /// Input selection background color.\n    pub selection: Hsla,\n    /// Sidebar background color.\n    pub sidebar: Hsla,\n    /// Sidebar accent background color.\n    pub sidebar_accent: Hsla,\n    /// Sidebar accent text color.\n    pub sidebar_accent_foreground: Hsla,\n    /// Sidebar border color.\n    pub sidebar_border: Hsla,\n    /// Sidebar text color.\n    pub sidebar_foreground: Hsla,\n    /// Sidebar primary background color.\n    pub sidebar_primary: Hsla,\n    /// Sidebar primary text color.\n    pub sidebar_primary_foreground: Hsla,\n    /// Skeleton background color.\n    pub skeleton: Hsla,\n    /// Slider bar background color.\n    pub slider_bar: Hsla,\n    /// Slider thumb background color.\n    pub slider_thumb: Hsla,\n    /// Success background color.\n    pub success: Hsla,\n    /// Success text color.\n    pub success_foreground: Hsla,\n    /// Success hover background color.\n    pub success_hover: Hsla,\n    /// Success active background color.\n    pub success_active: Hsla,\n    /// Switch background color.\n    pub switch: Hsla,\n    /// Switch thumb background color.\n    pub switch_thumb: Hsla,\n    /// Tab background color.\n    pub tab: Hsla,\n    /// Tab active background color.\n    pub tab_active: Hsla,\n    /// Tab active text color.\n    pub tab_active_foreground: Hsla,\n    /// TabBar background color.\n    pub tab_bar: Hsla,\n    /// TabBar segmented background color.\n    pub tab_bar_segmented: Hsla,\n    /// Tab text color.\n    pub tab_foreground: Hsla,\n    /// Table background color.\n    pub table: Hsla,\n    /// Table active item background color.\n    pub table_active: Hsla,\n    /// Table active item border color.\n    pub table_active_border: Hsla,\n    /// Stripe background color for even TableRow.\n    pub table_even: Hsla,\n    /// Table head background color.\n    pub table_head: Hsla,\n    /// Table head text color.\n    pub table_head_foreground: Hsla,\n    /// Table footer background color.\n    pub table_foot: Hsla,\n    /// Table footer text color.\n    pub table_foot_foreground: Hsla,\n    /// Table item hover background color.\n    pub table_hover: Hsla,\n    /// Table row border color.\n    pub table_row_border: Hsla,\n    /// TitleBar background color, use for Window title bar.\n    pub title_bar: Hsla,\n    /// TitleBar border color.\n    pub title_bar_border: Hsla,\n    /// Background color for Tiles.\n    pub tiles: Hsla,\n    /// Warning background color.\n    pub warning: Hsla,\n    /// Warning active background color.\n    pub warning_active: Hsla,\n    /// Warning hover background color.\n    pub warning_hover: Hsla,\n    /// Warning foreground color.\n    pub warning_foreground: Hsla,\n    /// Overlay background color.\n    pub overlay: Hsla,\n    /// Window border color.\n    ///\n    /// # Platform specific:\n    ///\n    /// This is only works on Linux, other platforms we can't change the window border color.\n    pub window_border: Hsla,\n\n    /// The base red color.\n    pub red: Hsla,\n    /// The base red light color.\n    pub red_light: Hsla,\n    /// The base green color.\n    pub green: Hsla,\n    /// The base green light color.\n    pub green_light: Hsla,\n    /// The base blue color.\n    pub blue: Hsla,\n    /// The base blue light color.\n    pub blue_light: Hsla,\n    /// The base yellow color.\n    pub yellow: Hsla,\n    /// The base yellow light color.\n    pub yellow_light: Hsla,\n    /// The base magenta color.\n    pub magenta: Hsla,\n    /// The base magenta light color.\n    pub magenta_light: Hsla,\n    /// The base cyan color.\n    pub cyan: Hsla,\n    /// The base cyan light color.\n    pub cyan_light: Hsla,\n}\n\nimpl ThemeColor {\n    /// Get the default light theme colors.\n    pub fn light() -> Arc<Self> {\n        DEFAULT_THEME_COLORS[&ThemeMode::Light].0.clone()\n    }\n\n    /// Get the default dark theme colors.\n    pub fn dark() -> Arc<Self> {\n        DEFAULT_THEME_COLORS[&ThemeMode::Dark].0.clone()\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/time/calendar.rs",
    "content": "use std::{borrow::Cow, rc::Rc};\n\nuse chrono::{Datelike, Local, NaiveDate};\nuse gpui::{\n    App, ClickEvent, Context, Div, ElementId, Empty, Entity, EventEmitter, FocusHandle,\n    InteractiveElement, IntoElement, ParentElement, Render, RenderOnce, SharedString, Stateful,\n    StatefulInteractiveElement, StyleRefinement, Styled, Window, prelude::FluentBuilder as _, px,\n    relative,\n};\nuse rust_i18n::t;\n\nuse crate::{\n    ActiveTheme, Disableable as _, IconName, Selectable, Sizable, Size, StyledExt as _,\n    button::{Button, ButtonVariants as _},\n    h_flex, v_flex,\n};\n\nuse super::utils::days_in_month;\n\n/// Events emitted by the calendar.\npub enum CalendarEvent {\n    /// The user selected a date.\n    Selected(Date),\n}\n\n/// The date of the calendar.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum Date {\n    Single(Option<NaiveDate>),\n    Range(Option<NaiveDate>, Option<NaiveDate>),\n}\n\nimpl std::fmt::Display for Date {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Single(Some(date)) => write!(f, \"{}\", date),\n            Self::Single(None) => write!(f, \"nil\"),\n            Self::Range(Some(start), Some(end)) => write!(f, \"{} - {}\", start, end),\n            Self::Range(None, None) => write!(f, \"nil\"),\n            Self::Range(Some(start), None) => write!(f, \"{} - nil\", start),\n            Self::Range(None, Some(end)) => write!(f, \"nil - {}\", end),\n        }\n    }\n}\n\nimpl From<NaiveDate> for Date {\n    fn from(date: NaiveDate) -> Self {\n        Self::Single(Some(date))\n    }\n}\n\nimpl From<(NaiveDate, NaiveDate)> for Date {\n    fn from((start, end): (NaiveDate, NaiveDate)) -> Self {\n        Self::Range(Some(start), Some(end))\n    }\n}\n\nimpl Date {\n    /// Check if the date is set.\n    pub fn is_some(&self) -> bool {\n        match self {\n            Self::Single(Some(_)) | Self::Range(Some(_), _) => true,\n            _ => false,\n        }\n    }\n\n    /// Check if the date is complete.\n    pub fn is_complete(&self) -> bool {\n        match self {\n            Self::Range(Some(_), Some(_)) => true,\n            Self::Single(Some(_)) => true,\n            _ => false,\n        }\n    }\n\n    /// Get the start date.\n    pub fn start(&self) -> Option<NaiveDate> {\n        match self {\n            Self::Single(Some(date)) => Some(*date),\n            Self::Range(Some(start), _) => Some(*start),\n            _ => None,\n        }\n    }\n\n    /// Get the end date.\n    pub fn end(&self) -> Option<NaiveDate> {\n        match self {\n            Self::Range(_, Some(end)) => Some(*end),\n            _ => None,\n        }\n    }\n\n    /// Return formatted date string.\n    pub fn format(&self, format: &str) -> Option<SharedString> {\n        match self {\n            Self::Single(Some(date)) => Some(date.format(format).to_string().into()),\n            Self::Range(Some(start), Some(end)) => {\n                Some(format!(\"{} - {}\", start.format(format), end.format(format)).into())\n            }\n            _ => None,\n        }\n    }\n\n    fn is_active(&self, v: &NaiveDate) -> bool {\n        let v = *v;\n        match self {\n            Self::Single(d) => Some(v) == *d,\n            Self::Range(start, end) => Some(v) == *start || Some(v) == *end,\n        }\n    }\n\n    fn is_single(&self) -> bool {\n        matches!(self, Self::Single(_))\n    }\n\n    fn is_in_range(&self, v: &NaiveDate) -> bool {\n        let v = *v;\n        match self {\n            Self::Range(start, end) => {\n                if let Some(start) = start {\n                    if let Some(end) = end {\n                        v >= *start && v <= *end\n                    } else {\n                        false\n                    }\n                } else {\n                    false\n                }\n            }\n            _ => false,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum ViewMode {\n    Day,\n    Month,\n    Year,\n}\n\nimpl ViewMode {\n    fn is_day(&self) -> bool {\n        matches!(self, Self::Day)\n    }\n\n    fn is_month(&self) -> bool {\n        matches!(self, Self::Month)\n    }\n\n    fn is_year(&self) -> bool {\n        matches!(self, Self::Year)\n    }\n}\n\n/// Matcher to match dates before and after the interval.\npub struct IntervalMatcher {\n    before: Option<NaiveDate>,\n    after: Option<NaiveDate>,\n}\n\n/// Matcher to match dates within the range.\npub struct RangeMatcher {\n    from: Option<NaiveDate>,\n    to: Option<NaiveDate>,\n}\n\n/// Matcher to match dates.\npub enum Matcher {\n    /// Match declare days of the week.\n    ///\n    /// Matcher::DayOfWeek(vec![0, 6])\n    /// Will match the days of the week that are Sunday and Saturday.\n    DayOfWeek(Vec<u32>),\n    /// Match the included days, except for those before and after the interval.\n    ///\n    /// Matcher::Interval(IntervalMatcher {\n    ///   before: Some(NaiveDate::from_ymd(2020, 1, 2)),\n    ///   after: Some(NaiveDate::from_ymd(2020, 1, 3)),\n    /// })\n    /// Will match the days that are not between 2020-01-02 and 2020-01-03.\n    Interval(IntervalMatcher),\n    /// Match the days within the range.\n    ///\n    /// Matcher::Range(RangeMatcher {\n    ///   from: Some(NaiveDate::from_ymd(2020, 1, 1)),\n    ///   to: Some(NaiveDate::from_ymd(2020, 1, 3)),\n    /// })\n    /// Will match the days that are between 2020-01-01 and 2020-01-03.\n    Range(RangeMatcher),\n    /// Match dates using a custom function.\n    ///\n    /// let matcher = Matcher::Custom(Box::new(|date: &NaiveDate| {\n    ///     date.day0() < 5\n    /// }));\n    /// Will match first 5 days of each month\n    Custom(Box<dyn Fn(&NaiveDate) -> bool + Send + Sync>),\n}\n\nimpl From<Vec<u32>> for Matcher {\n    fn from(days: Vec<u32>) -> Self {\n        Matcher::DayOfWeek(days)\n    }\n}\n\nimpl<F> From<F> for Matcher\nwhere\n    F: Fn(&NaiveDate) -> bool + Send + Sync + 'static,\n{\n    fn from(f: F) -> Self {\n        Matcher::Custom(Box::new(f))\n    }\n}\n\nimpl Matcher {\n    /// Create a new interval matcher.\n    pub fn interval(before: Option<NaiveDate>, after: Option<NaiveDate>) -> Self {\n        Matcher::Interval(IntervalMatcher { before, after })\n    }\n\n    /// Create a new range matcher.\n    pub fn range(from: Option<NaiveDate>, to: Option<NaiveDate>) -> Self {\n        Matcher::Range(RangeMatcher { from, to })\n    }\n\n    /// Create a new custom matcher.\n    pub fn custom<F>(f: F) -> Self\n    where\n        F: Fn(&NaiveDate) -> bool + Send + Sync + 'static,\n    {\n        Matcher::Custom(Box::new(f))\n    }\n\n    /// Check if the date matches the matcher.\n    pub fn is_match(&self, date: &Date) -> bool {\n        match date {\n            Date::Single(Some(date)) => self.matched(date),\n            Date::Range(Some(start), Some(end)) => self.matched(start) || self.matched(end),\n            _ => false,\n        }\n    }\n\n    fn matched(&self, date: &NaiveDate) -> bool {\n        match self {\n            Matcher::DayOfWeek(days) => days.contains(&date.weekday().num_days_from_sunday()),\n            Matcher::Interval(interval) => {\n                let before_check = interval.before.map_or(false, |before| date < &before);\n                let after_check = interval.after.map_or(false, |after| date > &after);\n                before_check || after_check\n            }\n            Matcher::Range(range) => {\n                let from_check = range.from.map_or(false, |from| date < &from);\n                let to_check = range.to.map_or(false, |to| date > &to);\n                !from_check && !to_check\n            }\n            Matcher::Custom(f) => f(date),\n        }\n    }\n}\n\n#[derive(IntoElement)]\npub struct Calendar {\n    id: ElementId,\n    size: Size,\n    state: Entity<CalendarState>,\n    style: StyleRefinement,\n    /// Number of the months view to show.\n    number_of_months: usize,\n}\n\n/// Use to store the state of the calendar.\npub struct CalendarState {\n    focus_handle: FocusHandle,\n    view_mode: ViewMode,\n    date: Date,\n    current_year: i32,\n    current_month: u8,\n    years: Vec<Vec<i32>>,\n    year_page: i32,\n    today: NaiveDate,\n    /// Number of the months view to show.\n    number_of_months: usize,\n    pub(crate) disabled_matcher: Option<Rc<Matcher>>,\n}\n\nimpl CalendarState {\n    /// Create a new calendar state.\n    pub fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {\n        let today = Local::now().naive_local().date();\n        Self {\n            focus_handle: cx.focus_handle(),\n            view_mode: ViewMode::Day,\n            date: Date::Single(None),\n            current_month: today.month() as u8,\n            current_year: today.year(),\n            years: vec![],\n            year_page: 0,\n            today,\n            number_of_months: 1,\n            disabled_matcher: None,\n        }\n        .year_range((today.year() - 50, today.year() + 50))\n    }\n\n    /// Set the disabled matcher of the calendar state.\n    pub fn disabled_matcher(mut self, matcher: impl Into<Matcher>) -> Self {\n        self.disabled_matcher = Some(Rc::new(matcher.into()));\n        self\n    }\n\n    /// Set the disabled matcher of the calendar.\n    ///\n    /// The disabled matcher will be used to disable the days that match the matcher.\n    pub fn set_disabled_matcher(\n        &mut self,\n        disabled: impl Into<Matcher>,\n        _: &mut Window,\n        _: &mut Context<Self>,\n    ) {\n        self.disabled_matcher = Some(Rc::new(disabled.into()));\n    }\n\n    /// Set the date of the calendar.\n    ///\n    /// When you set a range date, the mode will be automatically set to `Mode::Range`.\n    pub fn set_date(&mut self, date: impl Into<Date>, _: &mut Window, cx: &mut Context<Self>) {\n        let date = date.into();\n\n        let invalid = self\n            .disabled_matcher\n            .as_ref()\n            .map_or(false, |matcher| matcher.is_match(&date));\n\n        if invalid {\n            return;\n        }\n\n        self.date = date;\n        match self.date {\n            Date::Single(Some(date)) => {\n                self.current_month = date.month() as u8;\n                self.current_year = date.year();\n            }\n            Date::Range(Some(start), _) => {\n                self.current_month = start.month() as u8;\n                self.current_year = start.year();\n            }\n            _ => {}\n        }\n\n        cx.notify()\n    }\n\n    /// Get the date of the calendar.\n    pub fn date(&self) -> Date {\n        self.date\n    }\n\n    /// Set number of months to show.\n    pub fn set_number_of_months(\n        &mut self,\n        number_of_months: usize,\n        _: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        self.number_of_months = number_of_months;\n        cx.notify();\n    }\n\n    /// Set the year range of the calendar, default is 50 years before and after the current year.\n    ///\n    /// Each year page contains 20 years, so the range will be divided into chunks of 20 years is better.\n    pub fn year_range(mut self, range: (i32, i32)) -> Self {\n        self.years = (range.0..range.1)\n            .collect::<Vec<_>>()\n            .chunks(20)\n            .map(|chunk| chunk.to_vec())\n            .collect::<Vec<_>>();\n        self.year_page = self\n            .years\n            .iter()\n            .position(|years| years.contains(&self.current_year))\n            .unwrap_or(0) as i32;\n        self\n    }\n\n    /// Get year and month by offset month.\n    fn offset_year_month(&self, offset_month: usize) -> (i32, u32) {\n        let mut month = self.current_month as i32 + offset_month as i32;\n        let mut year = self.current_year;\n        while month < 1 {\n            month += 12;\n            year -= 1;\n        }\n        while month > 12 {\n            month -= 12;\n            year += 1;\n        }\n\n        (year, month as u32)\n    }\n\n    /// Returns the days of the month in a 2D vector to render on calendar.\n    fn days(&self) -> Vec<Vec<NaiveDate>> {\n        (0..self.number_of_months)\n            .flat_map(|offset| {\n                days_in_month(self.current_year, self.current_month as u32 + offset as u32)\n            })\n            .collect()\n    }\n\n    fn has_prev_year_page(&self) -> bool {\n        self.year_page > 0\n    }\n\n    fn has_next_year_page(&self) -> bool {\n        self.year_page < self.years.len() as i32 - 1\n    }\n\n    fn prev_year_page(&mut self, _: &ClickEvent, _: &mut Window, cx: &mut Context<Self>) {\n        if !self.has_prev_year_page() {\n            return;\n        }\n\n        self.year_page -= 1;\n        cx.notify()\n    }\n\n    fn next_year_page(&mut self, _: &ClickEvent, _: &mut Window, cx: &mut Context<Self>) {\n        if !self.has_next_year_page() {\n            return;\n        }\n\n        self.year_page += 1;\n        cx.notify()\n    }\n\n    fn prev_month(&mut self, _: &ClickEvent, _: &mut Window, cx: &mut Context<Self>) {\n        self.current_month = if self.current_month == 1 {\n            12\n        } else {\n            self.current_month - 1\n        };\n        self.current_year = if self.current_month == 12 {\n            self.current_year - 1\n        } else {\n            self.current_year\n        };\n        cx.notify()\n    }\n\n    fn next_month(&mut self, _: &ClickEvent, _: &mut Window, cx: &mut Context<Self>) {\n        self.current_month = if self.current_month == 12 {\n            1\n        } else {\n            self.current_month + 1\n        };\n        self.current_year = if self.current_month == 1 {\n            self.current_year + 1\n        } else {\n            self.current_year\n        };\n        cx.notify()\n    }\n\n    fn month_name(&self, offset_month: usize) -> SharedString {\n        let (_, month) = self.offset_year_month(offset_month);\n        match month {\n            1 => t!(\"Calendar.month.January\"),\n            2 => t!(\"Calendar.month.February\"),\n            3 => t!(\"Calendar.month.March\"),\n            4 => t!(\"Calendar.month.April\"),\n            5 => t!(\"Calendar.month.May\"),\n            6 => t!(\"Calendar.month.June\"),\n            7 => t!(\"Calendar.month.July\"),\n            8 => t!(\"Calendar.month.August\"),\n            9 => t!(\"Calendar.month.September\"),\n            10 => t!(\"Calendar.month.October\"),\n            11 => t!(\"Calendar.month.November\"),\n            12 => t!(\"Calendar.month.December\"),\n            _ => Cow::Borrowed(\"\"),\n        }\n        .into()\n    }\n\n    fn year_name(&self, offset_month: usize) -> SharedString {\n        let (year, _) = self.offset_year_month(offset_month);\n        year.to_string().into()\n    }\n\n    fn set_view_mode(&mut self, mode: ViewMode, _: &mut Window, cx: &mut Context<Self>) {\n        self.view_mode = mode;\n        cx.notify();\n    }\n\n    fn months(&self) -> Vec<SharedString> {\n        [\n            t!(\"Calendar.month.January\"),\n            t!(\"Calendar.month.February\"),\n            t!(\"Calendar.month.March\"),\n            t!(\"Calendar.month.April\"),\n            t!(\"Calendar.month.May\"),\n            t!(\"Calendar.month.June\"),\n            t!(\"Calendar.month.July\"),\n            t!(\"Calendar.month.August\"),\n            t!(\"Calendar.month.September\"),\n            t!(\"Calendar.month.October\"),\n            t!(\"Calendar.month.November\"),\n            t!(\"Calendar.month.December\"),\n        ]\n        .iter()\n        .map(|s| s.clone().into())\n        .collect()\n    }\n}\n\nimpl Render for CalendarState {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        Empty\n    }\n}\n\nimpl Calendar {\n    /// Create a new calendar element with [`CalendarState`].\n    pub fn new(state: &Entity<CalendarState>) -> Self {\n        Self {\n            id: (\"calendar\", state.entity_id()).into(),\n            size: Size::default(),\n            state: state.clone(),\n            style: StyleRefinement::default(),\n            number_of_months: 1,\n        }\n    }\n\n    /// Set number of months to show, default is 1.\n    pub fn number_of_months(mut self, number_of_months: usize) -> Self {\n        self.number_of_months = number_of_months;\n        self\n    }\n\n    fn render_day(\n        &self,\n        d: &NaiveDate,\n        offset_month: usize,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Stateful<Div> {\n        let state = self.state.read(cx);\n        let (_, month) = state.offset_year_month(offset_month);\n        let day = d.day();\n        let is_current_month = d.month() == month;\n        let is_active = state.date.is_active(d);\n        let is_in_range = state.date.is_in_range(d);\n\n        let date = *d;\n        let is_today = *d == state.today;\n        let disabled = state\n            .disabled_matcher\n            .as_ref()\n            .map_or(false, |disabled| disabled.matched(&date));\n\n        let date_id: SharedString = format!(\"{}_{}\", date.format(\"%Y-%m-%d\"), offset_month).into();\n\n        self.item_button(\n            date_id.clone(),\n            day.to_string(),\n            is_active,\n            is_in_range,\n            !is_current_month || disabled,\n            disabled,\n            window,\n            cx,\n        )\n        .when(is_today && !is_active, |this| {\n            this.border_1().border_color(cx.theme().border)\n        }) // Add border for today\n        .when(!disabled, |this| {\n            this.on_click(window.listener_for(\n                &self.state,\n                move |view, _: &ClickEvent, window, cx| {\n                    if view.date.is_single() {\n                        view.set_date(date, window, cx);\n                        cx.emit(CalendarEvent::Selected(view.date()));\n                    } else {\n                        let start = view.date.start();\n                        let end = view.date.end();\n\n                        if start.is_none() && end.is_none() {\n                            view.set_date(Date::Range(Some(date), None), window, cx);\n                        } else if start.is_some() && end.is_none() {\n                            if date < start.unwrap() {\n                                view.set_date(Date::Range(Some(date), None), window, cx);\n                            } else {\n                                view.set_date(\n                                    Date::Range(Some(start.unwrap()), Some(date)),\n                                    window,\n                                    cx,\n                                );\n                            }\n                        } else {\n                            view.set_date(Date::Range(Some(date), None), window, cx);\n                        }\n\n                        if view.date.is_complete() {\n                            cx.emit(CalendarEvent::Selected(view.date()));\n                        }\n                    }\n                },\n            ))\n        })\n    }\n\n    fn render_header(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let state = self.state.read(cx);\n        let current_year = state.current_year;\n        let view_mode = state.view_mode;\n        let disabled = view_mode.is_month();\n        let multiple_months = self.number_of_months > 1;\n        let icon_size = match self.size {\n            Size::Small => Size::Small,\n            Size::Large => Size::Medium,\n            _ => Size::Medium,\n        };\n\n        h_flex()\n            .gap_0p5()\n            .justify_between()\n            .items_center()\n            .child(\n                Button::new(\"prev\")\n                    .icon(IconName::ArrowLeft)\n                    .tab_stop(false)\n                    .ghost()\n                    .disabled(disabled)\n                    .with_size(icon_size)\n                    .when(view_mode.is_day(), |this| {\n                        this.on_click(window.listener_for(&self.state, CalendarState::prev_month))\n                    })\n                    .when(view_mode.is_year(), |this| {\n                        this.when(!state.has_prev_year_page(), |this| this.disabled(true))\n                            .on_click(\n                                window.listener_for(&self.state, CalendarState::prev_year_page),\n                            )\n                    }),\n            )\n            .when(!multiple_months, |this| {\n                this.child(\n                    h_flex()\n                        .justify_center()\n                        .gap_3()\n                        .child(\n                            Button::new(\"month\")\n                                .ghost()\n                                .label(state.month_name(0))\n                                .compact()\n                                .tab_stop(false)\n                                .with_size(self.size)\n                                .selected(view_mode.is_month())\n                                .on_click(window.listener_for(\n                                    &self.state,\n                                    move |view, _, window, cx| {\n                                        if view_mode.is_month() {\n                                            view.set_view_mode(ViewMode::Day, window, cx);\n                                        } else {\n                                            view.set_view_mode(ViewMode::Month, window, cx);\n                                        }\n                                        cx.notify();\n                                    },\n                                )),\n                        )\n                        .child(\n                            Button::new(\"year\")\n                                .ghost()\n                                .label(current_year.to_string())\n                                .compact()\n                                .tab_stop(false)\n                                .with_size(self.size)\n                                .selected(view_mode.is_year())\n                                .on_click(window.listener_for(\n                                    &self.state,\n                                    |view, _, window, cx| {\n                                        if view.view_mode.is_year() {\n                                            view.set_view_mode(ViewMode::Day, window, cx);\n                                        } else {\n                                            view.set_view_mode(ViewMode::Year, window, cx);\n                                        }\n                                        cx.notify();\n                                    },\n                                )),\n                        ),\n                )\n            })\n            .when(multiple_months, |this| {\n                this.child(h_flex().flex_1().justify_around().children(\n                    (0..self.number_of_months).map(|n| {\n                        h_flex()\n                            .justify_center()\n                            .map(|this| match self.size {\n                                Size::Small => this.gap_2(),\n                                Size::Large => this.gap_4(),\n                                _ => this.gap_3(),\n                            })\n                            .child(state.month_name(n))\n                            .child(state.year_name(n))\n                    }),\n                ))\n            })\n            .child(\n                Button::new(\"next\")\n                    .icon(IconName::ArrowRight)\n                    .ghost()\n                    .tab_stop(false)\n                    .disabled(disabled)\n                    .with_size(icon_size)\n                    .when(view_mode.is_day(), |this| {\n                        this.on_click(window.listener_for(&self.state, CalendarState::next_month))\n                    })\n                    .when(view_mode.is_year(), |this| {\n                        this.when(!state.has_next_year_page(), |this| this.disabled(true))\n                            .on_click(\n                                window.listener_for(&self.state, CalendarState::next_year_page),\n                            )\n                    }),\n            )\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    fn item_button(\n        &self,\n        id: impl Into<ElementId>,\n        label: impl Into<SharedString>,\n        active: bool,\n        secondary_active: bool,\n        muted: bool,\n        disabled: bool,\n        _: &mut Window,\n        cx: &mut App,\n    ) -> Stateful<Div> {\n        h_flex()\n            .id(id.into())\n            .map(|this| match self.size {\n                Size::Small => this.size_7().rounded(cx.theme().radius / 2.),\n                Size::Large => this.size_10().rounded(cx.theme().radius * 2.),\n                _ => this.size_9().rounded(cx.theme().radius),\n            })\n            .justify_center()\n            .when(muted, |this| {\n                this.text_color(if disabled {\n                    cx.theme().muted_foreground.opacity(0.3)\n                } else {\n                    cx.theme().muted_foreground\n                })\n            })\n            .when(secondary_active, |this| {\n                this.bg(if muted {\n                    cx.theme().accent.opacity(0.5)\n                } else {\n                    cx.theme().accent\n                })\n                .text_color(cx.theme().accent_foreground)\n            })\n            .when(!active && !disabled, |this| {\n                this.hover(|this| {\n                    this.bg(cx.theme().accent)\n                        .text_color(cx.theme().accent_foreground)\n                })\n            })\n            .when(active, |this| {\n                this.bg(cx.theme().primary)\n                    .text_color(cx.theme().primary_foreground)\n            })\n            .child(label.into())\n    }\n\n    fn render_days(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let state = self.state.read(cx);\n        let weeks = [\n            t!(\"Calendar.week.0\"),\n            t!(\"Calendar.week.1\"),\n            t!(\"Calendar.week.2\"),\n            t!(\"Calendar.week.3\"),\n            t!(\"Calendar.week.4\"),\n            t!(\"Calendar.week.5\"),\n            t!(\"Calendar.week.6\"),\n        ];\n\n        h_flex()\n            .map(|this| match self.size {\n                Size::Small => this.gap_3().text_sm(),\n                Size::Large => this.gap_5().text_base(),\n                _ => this.gap_4().text_sm(),\n            })\n            .justify_between()\n            .children(\n                state\n                    .days()\n                    .chunks(5)\n                    .enumerate()\n                    .map(|(offset_month, days)| {\n                        v_flex()\n                            .gap_0p5()\n                            .child(\n                                h_flex().gap_0p5().justify_between().children(\n                                    weeks\n                                        .iter()\n                                        .map(|week| self.render_week(week.clone(), window, cx)),\n                                ),\n                            )\n                            .children(days.iter().map(|week| {\n                                h_flex().gap_0p5().justify_between().children(\n                                    week.iter()\n                                        .map(|d| self.render_day(d, offset_month, window, cx)),\n                                )\n                            }))\n                    }),\n            )\n    }\n\n    fn render_week(&self, week: impl Into<SharedString>, _: &mut Window, cx: &mut App) -> Div {\n        h_flex()\n            .map(|this| match self.size {\n                Size::Small => this.size_7().rounded(cx.theme().radius / 2.0),\n                Size::Large => this.size_10().rounded(cx.theme().radius),\n                _ => this.size_9().rounded(cx.theme().radius),\n            })\n            .justify_center()\n            .text_color(cx.theme().muted_foreground)\n            .text_sm()\n            .child(week.into())\n    }\n\n    fn render_months(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let state = self.state.read(cx);\n        let months = state.months();\n        let current_month = state.current_month;\n\n        h_flex()\n            .mt_3()\n            .gap_0p5()\n            .gap_y_3()\n            .map(|this| match self.size {\n                Size::Small => this.mt_2().gap_y_2().w(px(208.)),\n                Size::Large => this.mt_4().gap_y_4().w(px(292.)),\n                _ => this.mt_3().gap_y_3().w(px(264.)),\n            })\n            .justify_between()\n            .flex_wrap()\n            .children(\n                months\n                    .iter()\n                    .enumerate()\n                    .map(|(ix, month)| {\n                        let active = (ix + 1) as u8 == current_month;\n\n                        self.item_button(\n                            ix,\n                            month.to_string(),\n                            active,\n                            false,\n                            false,\n                            false,\n                            window,\n                            cx,\n                        )\n                        .w(relative(0.3))\n                        .text_sm()\n                        .on_click(window.listener_for(\n                            &self.state,\n                            move |view, _, window, cx| {\n                                view.current_month = (ix + 1) as u8;\n                                view.set_view_mode(ViewMode::Day, window, cx);\n                                cx.notify();\n                            },\n                        ))\n                    })\n                    .collect::<Vec<_>>(),\n            )\n    }\n\n    fn render_years(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let state = self.state.read(cx);\n        let current_year = state.current_year;\n        let current_page_years = &self.state.read(cx).years[state.year_page as usize].clone();\n\n        h_flex()\n            .id(\"years\")\n            .gap_0p5()\n            .map(|this| match self.size {\n                Size::Small => this.mt_2().gap_y_2().w(px(208.)),\n                Size::Large => this.mt_4().gap_y_4().w(px(292.)),\n                _ => this.mt_3().gap_y_3().w(px(264.)),\n            })\n            .justify_between()\n            .flex_wrap()\n            .children(\n                current_page_years\n                    .iter()\n                    .enumerate()\n                    .map(|(ix, year)| {\n                        let year = *year;\n                        let active = year == current_year;\n\n                        self.item_button(\n                            ix,\n                            year.to_string(),\n                            active,\n                            false,\n                            false,\n                            false,\n                            window,\n                            cx,\n                        )\n                        .w(relative(0.2))\n                        .on_click(window.listener_for(\n                            &self.state,\n                            move |view, _, window, cx| {\n                                view.current_year = year;\n                                view.set_view_mode(ViewMode::Day, window, cx);\n                                cx.notify();\n                            },\n                        ))\n                    })\n                    .collect::<Vec<_>>(),\n            )\n    }\n}\n\nimpl Sizable for Calendar {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\n\nimpl Styled for Calendar {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl EventEmitter<CalendarEvent> for CalendarState {}\nimpl RenderOnce for Calendar {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let view_mode = self.state.read(cx).view_mode;\n        let number_of_months = self.number_of_months;\n        self.state.update(cx, |state, _| {\n            state.number_of_months = number_of_months;\n        });\n\n        v_flex()\n            .id(self.id.clone())\n            .track_focus(&self.state.read(cx).focus_handle)\n            .border_1()\n            .border_color(cx.theme().border)\n            .rounded(cx.theme().radius_lg)\n            .p_3()\n            .gap_0p5()\n            .refine_style(&self.style)\n            .child(self.render_header(window, cx))\n            .child(\n                v_flex()\n                    .when(view_mode.is_day(), |this| {\n                        this.child(self.render_days(window, cx))\n                    })\n                    .when(view_mode.is_month(), |this| {\n                        this.child(self.render_months(window, cx))\n                    })\n                    .when(view_mode.is_year(), |this| {\n                        this.child(self.render_years(window, cx))\n                    }),\n            )\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use chrono::NaiveDate;\n\n    use super::Date;\n\n    #[test]\n    fn test_date_to_string() {\n        let date = Date::Single(Some(NaiveDate::from_ymd_opt(2024, 8, 3).unwrap()));\n        assert_eq!(date.to_string(), \"2024-08-03\");\n\n        let date = Date::Single(None);\n        assert_eq!(date.to_string(), \"nil\");\n\n        let date = Date::Range(\n            Some(NaiveDate::from_ymd_opt(2024, 8, 3).unwrap()),\n            Some(NaiveDate::from_ymd_opt(2024, 8, 5).unwrap()),\n        );\n        assert_eq!(date.to_string(), \"2024-08-03 - 2024-08-05\");\n\n        let date = Date::Range(Some(NaiveDate::from_ymd_opt(2024, 8, 3).unwrap()), None);\n        assert_eq!(date.to_string(), \"2024-08-03 - nil\");\n\n        let date = Date::Range(None, Some(NaiveDate::from_ymd_opt(2024, 8, 5).unwrap()));\n        assert_eq!(date.to_string(), \"nil - 2024-08-05\");\n\n        let date = Date::Range(None, None);\n        assert_eq!(date.to_string(), \"nil\");\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/time/date_picker.rs",
    "content": "use std::rc::Rc;\n\nuse chrono::NaiveDate;\nuse gpui::{\n    App, AppContext, ClickEvent, Context, ElementId, Empty, Entity, EventEmitter, FocusHandle,\n    Focusable, InteractiveElement as _, IntoElement, KeyBinding, MouseButton, ParentElement as _,\n    Render, RenderOnce, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled,\n    Subscription, Window, anchored, deferred, div, prelude::FluentBuilder as _, px,\n};\nuse rust_i18n::t;\n\nuse crate::{\n    ActiveTheme, Disableable, Icon, IconName, Sizable, Size, StyleSized as _, StyledExt as _,\n    actions::{Cancel, Confirm},\n    button::{Button, ButtonVariants as _},\n    h_flex,\n    input::{Delete, clear_button, input_style},\n    v_flex,\n};\n\nuse super::calendar::{Calendar, CalendarEvent, CalendarState, Date, Matcher};\n\nconst CONTEXT: &'static str = \"DatePicker\";\npub(crate) fn init(cx: &mut App) {\n    cx.bind_keys([\n        KeyBinding::new(\"enter\", Confirm { secondary: false }, Some(CONTEXT)),\n        KeyBinding::new(\"escape\", Cancel, Some(CONTEXT)),\n        KeyBinding::new(\"delete\", Delete, Some(CONTEXT)),\n        KeyBinding::new(\"backspace\", Delete, Some(CONTEXT)),\n    ])\n}\n\n/// Events emitted by the DatePicker.\n#[derive(Clone)]\npub enum DatePickerEvent {\n    Change(Date),\n}\n\n/// Preset value for DateRangePreset.\n#[derive(Clone)]\npub enum DateRangePresetValue {\n    Single(NaiveDate),\n    Range(NaiveDate, NaiveDate),\n}\n\n/// Preset for date range selection.\n#[derive(Clone)]\npub struct DateRangePreset {\n    label: SharedString,\n    value: DateRangePresetValue,\n}\n\nimpl DateRangePreset {\n    /// Creates a new DateRangePreset with a date.\n    pub fn single(label: impl Into<SharedString>, date: NaiveDate) -> Self {\n        DateRangePreset {\n            label: label.into(),\n            value: DateRangePresetValue::Single(date),\n        }\n    }\n    /// Creates a new DateRangePreset with a range of dates.\n    pub fn range(label: impl Into<SharedString>, start: NaiveDate, end: NaiveDate) -> Self {\n        DateRangePreset {\n            label: label.into(),\n            value: DateRangePresetValue::Range(start, end),\n        }\n    }\n}\n\n/// Use to store the state of the date picker.\npub struct DatePickerState {\n    focus_handle: FocusHandle,\n    date: Date,\n    open: bool,\n    calendar: Entity<CalendarState>,\n    date_format: SharedString,\n    number_of_months: usize,\n    disabled_matcher: Option<Rc<Matcher>>,\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl Focusable for DatePickerState {\n    fn focus_handle(&self, _: &App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\nimpl EventEmitter<DatePickerEvent> for DatePickerState {}\n\nimpl DatePickerState {\n    /// Create a date state.\n    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self::new_with_range(false, window, cx)\n    }\n\n    /// Create a date state with range mode.\n    pub fn range(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        Self::new_with_range(true, window, cx)\n    }\n\n    fn new_with_range(is_range: bool, window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let date = if is_range {\n            Date::Range(None, None)\n        } else {\n            Date::Single(None)\n        };\n\n        let calendar = cx.new(|cx| {\n            let mut this = CalendarState::new(window, cx);\n            this.set_date(date, window, cx);\n            this\n        });\n\n        let _subscriptions = vec![cx.subscribe_in(\n            &calendar,\n            window,\n            |this, _, ev: &CalendarEvent, window, cx| match ev {\n                CalendarEvent::Selected(date) => {\n                    this.update_date(*date, true, window, cx);\n                    this.focus_handle.focus(window, cx);\n                }\n            },\n        )];\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            date,\n            calendar,\n            open: false,\n            date_format: \"%Y/%m/%d\".into(),\n            number_of_months: 1,\n            disabled_matcher: None,\n            _subscriptions,\n        }\n    }\n\n    /// Set the date format of the date picker to display in Input, default: \"%Y/%m/%d\".\n    pub fn date_format(mut self, format: impl Into<SharedString>) -> Self {\n        self.date_format = format.into();\n        self\n    }\n\n    /// Set the number of months calendar view to display, default is 1.\n    pub fn number_of_months(mut self, number_of_months: usize) -> Self {\n        self.number_of_months = number_of_months;\n        self\n    }\n\n    /// Get the date of the date picker.\n    pub fn date(&self) -> Date {\n        self.date\n    }\n\n    /// Set the date of the date picker.\n    pub fn set_date(&mut self, date: impl Into<Date>, window: &mut Window, cx: &mut Context<Self>) {\n        self.update_date(date.into(), false, window, cx);\n    }\n\n    /// Set the disabled match for the calendar.\n    pub fn disabled_matcher(mut self, disabled: impl Into<Matcher>) -> Self {\n        self.disabled_matcher = Some(Rc::new(disabled.into()));\n        self\n    }\n\n    fn update_date(&mut self, date: Date, emit: bool, window: &mut Window, cx: &mut Context<Self>) {\n        self.date = date;\n        self.calendar.update(cx, |view, cx| {\n            view.set_date(date, window, cx);\n        });\n        self.open = false;\n        if emit {\n            cx.emit(DatePickerEvent::Change(date));\n        }\n        cx.notify();\n    }\n\n    /// Set the disabled matcher of the date picker.\n    fn set_canlendar_disabled_matcher(&mut self, _: &mut Window, cx: &mut Context<Self>) {\n        let matcher = self.disabled_matcher.clone();\n        self.calendar.update(cx, |state, _| {\n            state.disabled_matcher = matcher;\n        });\n    }\n\n    fn on_escape(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {\n        if !self.open {\n            cx.propagate();\n        }\n\n        self.focus_back_if_need(window, cx);\n        self.open = false;\n\n        cx.notify();\n    }\n\n    fn on_enter(&mut self, _: &Confirm, _: &mut Window, cx: &mut Context<Self>) {\n        if !self.open {\n            self.open = true;\n            cx.notify();\n        }\n    }\n\n    fn on_delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {\n        self.clean(&ClickEvent::default(), window, cx);\n    }\n\n    // To focus the Picker Input, if current focus in is on the container.\n    //\n    // This is because mouse down out the Calendar, GPUI will move focus to the container.\n    // So we need to move focus back to the Picker Input.\n    //\n    // But if mouse down target is some other focusable element (e.g.: [`crate::Input`]), we should not move focus.\n    fn focus_back_if_need(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        if !self.open {\n            return;\n        }\n\n        if let Some(focused) = window.focused(cx) {\n            if focused.contains(&self.focus_handle, window) {\n                self.focus_handle.focus(window, cx);\n            }\n        }\n    }\n\n    fn clean(&mut self, _: &gpui::ClickEvent, window: &mut Window, cx: &mut Context<Self>) {\n        cx.stop_propagation();\n        match self.date {\n            Date::Single(_) => {\n                self.update_date(Date::Single(None), true, window, cx);\n            }\n            Date::Range(_, _) => {\n                self.update_date(Date::Range(None, None), true, window, cx);\n            }\n        }\n    }\n\n    fn toggle_calendar(&mut self, _: &gpui::ClickEvent, _: &mut Window, cx: &mut Context<Self>) {\n        self.open = !self.open;\n        cx.notify();\n    }\n\n    fn select_preset(\n        &mut self,\n        preset: &DateRangePreset,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        match preset.value {\n            DateRangePresetValue::Single(single) => {\n                self.update_date(Date::Single(Some(single)), true, window, cx)\n            }\n            DateRangePresetValue::Range(start, end) => {\n                self.update_date(Date::Range(Some(start), Some(end)), true, window, cx)\n            }\n        }\n    }\n}\n\n/// A DatePicker element.\n#[derive(IntoElement)]\npub struct DatePicker {\n    id: ElementId,\n    style: StyleRefinement,\n    state: Entity<DatePickerState>,\n    cleanable: bool,\n    placeholder: Option<SharedString>,\n    size: Size,\n    number_of_months: usize,\n    presets: Option<Vec<DateRangePreset>>,\n    appearance: bool,\n    disabled: bool,\n}\n\nimpl Sizable for DatePicker {\n    fn with_size(mut self, size: impl Into<Size>) -> Self {\n        self.size = size.into();\n        self\n    }\n}\nimpl Focusable for DatePicker {\n    fn focus_handle(&self, cx: &App) -> FocusHandle {\n        self.state.focus_handle(cx)\n    }\n}\n\nimpl Styled for DatePicker {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl Disableable for DatePicker {\n    fn disabled(mut self, disabled: bool) -> Self {\n        self.disabled = disabled;\n        self\n    }\n}\n\nimpl Render for DatePickerState {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl gpui::IntoElement {\n        Empty\n    }\n}\n\nimpl DatePicker {\n    /// Create a new DatePicker with the given [`DatePickerState`].\n    pub fn new(state: &Entity<DatePickerState>) -> Self {\n        Self {\n            id: (\"date-picker\", state.entity_id()).into(),\n            state: state.clone(),\n            cleanable: false,\n            placeholder: None,\n            size: Size::default(),\n            style: StyleRefinement::default(),\n            number_of_months: 2,\n            presets: None,\n            appearance: true,\n            disabled: false,\n        }\n    }\n\n    /// Set the placeholder of the date picker, default: \"\".\n    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {\n        self.placeholder = Some(placeholder.into());\n        self\n    }\n\n    /// Set whether to show the clear button when the input field is not empty, default is false.\n    pub fn cleanable(mut self, cleanable: bool) -> Self {\n        self.cleanable = cleanable;\n        self\n    }\n\n    /// Set preset ranges for the date picker.\n    pub fn presets(mut self, presets: Vec<DateRangePreset>) -> Self {\n        self.presets = Some(presets);\n        self\n    }\n\n    /// Set number of months to display in the calendar, default is 2.\n    pub fn number_of_months(mut self, number_of_months: usize) -> Self {\n        self.number_of_months = number_of_months;\n        self\n    }\n\n    /// Set appearance of the date picker, if false, the date picker will be in a minimal style.\n    pub fn appearance(mut self, appearance: bool) -> Self {\n        self.appearance = appearance;\n        self\n    }\n}\n\nimpl RenderOnce for DatePicker {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        self.state.update(cx, |state, cx| {\n            state.set_canlendar_disabled_matcher(window, cx);\n        });\n\n        // This for keep focus border style, when click on the popup.\n        let is_focused = self.focus_handle(cx).contains_focused(window, cx);\n        let state = self.state.read(cx);\n        let show_clean = self.cleanable && state.date.is_some();\n        let placeholder = self\n            .placeholder\n            .clone()\n            .unwrap_or_else(|| t!(\"DatePicker.placeholder\").into());\n        let display_title = state\n            .date\n            .format(&state.date_format)\n            .unwrap_or(placeholder.clone());\n\n        let (bg, fg) = input_style(self.disabled, cx);\n\n        div()\n            .id(self.id.clone())\n            .key_context(CONTEXT)\n            .track_focus(&self.focus_handle(cx).tab_stop(true))\n            .on_action(window.listener_for(&self.state, DatePickerState::on_enter))\n            .on_action(window.listener_for(&self.state, DatePickerState::on_delete))\n            .when(state.open, |this| {\n                this.on_action(window.listener_for(&self.state, DatePickerState::on_escape))\n            })\n            .flex_none()\n            .w_full()\n            .relative()\n            .input_text_size(self.size)\n            .refine_style(&self.style)\n            .child(\n                div()\n                    .id(\"date-picker-input\")\n                    .relative()\n                    .flex()\n                    .items_center()\n                    .justify_between()\n                    .when(self.appearance, |this| {\n                        this.bg(bg)\n                            .text_color(fg)\n                            .when(self.disabled, |this| this.opacity(0.5))\n                            .border_1()\n                            .border_color(cx.theme().input)\n                            .rounded(cx.theme().radius)\n                            .when(cx.theme().shadow, |this| this.shadow_xs())\n                            .when(is_focused, |this| this.focused_border(cx))\n                    })\n                    .overflow_hidden()\n                    .input_text_size(self.size)\n                    .input_size(self.size)\n                    .when(!state.open && !self.disabled, |this| {\n                        this.on_click(\n                            window.listener_for(&self.state, DatePickerState::toggle_calendar),\n                        )\n                    })\n                    .child(\n                        h_flex()\n                            .w_full()\n                            .items_center()\n                            .justify_between()\n                            .gap_1()\n                            .child(\n                                div()\n                                    .w_full()\n                                    .overflow_hidden()\n                                    .when(!state.date.is_some(), |this| {\n                                        this.text_color(cx.theme().muted_foreground)\n                                    })\n                                    .child(display_title),\n                            )\n                            .when(!self.disabled, |this| {\n                                this.when(show_clean, |this| {\n                                    this.child(clear_button(cx).on_click(\n                                        window.listener_for(&self.state, DatePickerState::clean),\n                                    ))\n                                })\n                                .when(!show_clean, |this| {\n                                    this.child(\n                                        Icon::new(IconName::Calendar)\n                                            .xsmall()\n                                            .text_color(cx.theme().muted_foreground),\n                                    )\n                                })\n                            }),\n                    ),\n            )\n            .when(state.open, |this| {\n                this.child(\n                    deferred(\n                        anchored().snap_to_window_with_margin(px(8.)).child(\n                            div()\n                                .occlude()\n                                .mt_1p5()\n                                .p_3()\n                                .border_1()\n                                .border_color(cx.theme().border)\n                                .shadow_lg()\n                                .rounded((cx.theme().radius * 2.).min(px(8.)))\n                                .bg(cx.theme().popover)\n                                .text_color(cx.theme().popover_foreground)\n                                .on_mouse_up_out(\n                                    MouseButton::Left,\n                                    window.listener_for(&self.state, |view, _, window, cx| {\n                                        view.on_escape(&Cancel, window, cx);\n                                    }),\n                                )\n                                .child(\n                                    h_flex()\n                                        .gap_3()\n                                        .h_full()\n                                        .items_start()\n                                        .when_some(self.presets.clone(), |this, presets| {\n                                            this.child(\n                                                v_flex().my_1().gap_2().justify_end().children(\n                                                    presets.into_iter().enumerate().map(\n                                                        |(i, preset)| {\n                                                            Button::new((\"preset\", i))\n                                                                .small()\n                                                                .ghost()\n                                                                .tab_stop(false)\n                                                                .label(preset.label.clone())\n                                                                .on_click(window.listener_for(\n                                                                    &self.state,\n                                                                    move |this, _, window, cx| {\n                                                                        this.select_preset(\n                                                                            &preset, window, cx,\n                                                                        );\n                                                                    },\n                                                                ))\n                                                        },\n                                                    ),\n                                                ),\n                                            )\n                                        })\n                                        .child(\n                                            Calendar::new(&state.calendar)\n                                                .number_of_months(self.number_of_months)\n                                                .border_0()\n                                                .rounded_none()\n                                                .p_0()\n                                                .with_size(self.size),\n                                        ),\n                                ),\n                        ),\n                    )\n                    .with_priority(2),\n                )\n            })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/time/mod.rs",
    "content": "pub mod calendar;\npub mod date_picker;\nmod utils;\n"
  },
  {
    "path": "crates/ui/src/time/utils.rs",
    "content": "use chrono::{Datelike, Duration, NaiveDate};\n\ntrait NaiveDateExt {\n    fn days_in_month(&self) -> i32;\n    fn is_leap_year(&self) -> bool;\n}\n\nimpl NaiveDateExt for chrono::NaiveDate {\n    fn days_in_month(&self) -> i32 {\n        let month = self.month();\n        match month {\n            1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,\n            4 | 6 | 9 | 11 => 30,\n            2 => {\n                if self.is_leap_year() {\n                    29\n                } else {\n                    28\n                }\n            }\n            _ => panic!(\"Invalid month: {}\", month),\n        }\n    }\n\n    fn is_leap_year(&self) -> bool {\n        let year = self.year();\n        return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);\n    }\n}\n\npub(crate) fn days_in_month(year: i32, month: u32) -> Vec<Vec<NaiveDate>> {\n    let mut year = year;\n    let mut month = month;\n    if month > 12 {\n        year += 1;\n        month = 1;\n    }\n    if month < 1 {\n        year -= 1;\n        month = 12;\n    }\n\n    let date = NaiveDate::from_ymd_opt(year, month, 1).unwrap();\n    let num_days = date.days_in_month();\n    let start_weekday = date.weekday().num_days_from_sunday();\n\n    // Get the days in the month, 2023-02 will returns\n    // \"29|30|31| 1| 2| 3| 4\",\n    // \" 5| 6| 7| 8| 9|10|11\",\n    // \"12|13|14|15|16|17|18\",\n    // \"19|20|21|22|23|24|25\",\n    // \"26|27|28| 1| 2| 3| 4\",\n    let mut days = vec![];\n    for n in 0..5 {\n        let mut week_days = vec![];\n        for weekday in 0..7 {\n            let (mut y, mut m) = (year, month);\n\n            // If the day is less than the start weekday, we need to go back to the previous month.\n            if n == 0 && weekday < start_weekday {\n                m = if m == 1 { 12 } else { m - 1 };\n                y = if m == 1 { year - 1 } else { y };\n            }\n\n            // If start_weekday is 3, and n is 0 and weekday is 3, then day is 1.\n            // If start_weekday is 3, and n is 1 and weekday is 4, then day is 9.\n            let day = n * 7 + weekday as i32 - start_weekday as i32;\n\n            // If the day is greater than the number of days in the month, we need to go to the next month.\n            if day > num_days {\n                m = if m == 12 { 1 } else { m + 1 };\n                y = if m == 1 { year + 1 } else { y };\n            }\n\n            #[allow(clippy::expect_fun_call)]\n            let date = date\n                .checked_add_signed(Duration::days(day as i64))\n                .expect(&format!(\"invalid date {}-{} days {}\", y, m, day));\n            week_days.push(date);\n        }\n\n        days.push(week_days);\n    }\n\n    days\n}\n\n#[cfg(test)]\nmod tests {\n    use chrono::{Datelike, NaiveDate};\n\n    use super::{days_in_month, NaiveDateExt};\n\n    #[test]\n    fn test_days_in_month() {\n        assert_eq!(\n            NaiveDate::from_ymd_opt(2024, 2, 1).unwrap().days_in_month(),\n            29\n        );\n        assert_eq!(\n            NaiveDate::from_ymd_opt(2023, 2, 1).unwrap().days_in_month(),\n            28\n        );\n        assert_eq!(\n            NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().days_in_month(),\n            31\n        );\n        assert_eq!(\n            NaiveDate::from_ymd_opt(2023, 4, 1).unwrap().days_in_month(),\n            30\n        );\n    }\n\n    #[test]\n    fn test_days() {\n        #[track_caller]\n        fn assert_case(date: NaiveDate, expected: Vec<&str>) {\n            let out = days_in_month(date.year(), date.month())\n                .iter()\n                .map(|week| {\n                    week.iter()\n                        .map(|d| {\n                            if d.year() == date.year() && d.month() == date.month() {\n                                format!(\"{:2}\", d.day())\n                            } else if d.year() == date.year() {\n                                format!(\"{}-{}\", d.month(), d.day())\n                            } else {\n                                format!(\"{}-{}-{}\", d.year(), d.month(), d.day())\n                            }\n                        })\n                        .collect::<Vec<_>>()\n                        .join(\"|\")\n                })\n                .collect::<Vec<_>>();\n\n            assert_eq!(out, expected);\n        }\n\n        assert_case(\n            NaiveDate::from_ymd_opt(2024, 8, 1).unwrap(),\n            vec![\n                \"7-28|7-29|7-30|7-31| 1| 2| 3\",\n                \" 4| 5| 6| 7| 8| 9|10\",\n                \"11|12|13|14|15|16|17\",\n                \"18|19|20|21|22|23|24\",\n                \"25|26|27|28|29|30|31\",\n            ],\n        );\n        assert_case(\n            NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),\n            vec![\n                \"2024-12-29|2024-12-30|2024-12-31| 1| 2| 3| 4\",\n                \" 5| 6| 7| 8| 9|10|11\",\n                \"12|13|14|15|16|17|18\",\n                \"19|20|21|22|23|24|25\",\n                \"26|27|28|29|30|31|2-1\",\n            ],\n        );\n\n        assert_case(\n            NaiveDate::from_ymd_opt(2024, 2, 1).unwrap(),\n            vec![\n                \"1-28|1-29|1-30|1-31| 1| 2| 3\",\n                \" 4| 5| 6| 7| 8| 9|10\",\n                \"11|12|13|14|15|16|17\",\n                \"18|19|20|21|22|23|24\",\n                \"25|26|27|28|29|3-1|3-2\",\n            ],\n        );\n        assert_case(\n            NaiveDate::from_ymd_opt(2023, 2, 20).unwrap(),\n            vec![\n                \"1-29|1-30|1-31| 1| 2| 3| 4\",\n                \" 5| 6| 7| 8| 9|10|11\",\n                \"12|13|14|15|16|17|18\",\n                \"19|20|21|22|23|24|25\",\n                \"26|27|28|3-1|3-2|3-3|3-4\",\n            ],\n        );\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/title_bar.rs",
    "content": "use std::rc::Rc;\n\nuse crate::{\n    ActiveTheme, Icon, IconName, InteractiveElementExt as _, Sizable as _, StyledExt, h_flex,\n};\nuse gpui::{\n    AnyElement, App, ClickEvent, Context, Decorations, Hsla, InteractiveElement, IntoElement,\n    MouseButton, ParentElement, Pixels, Render, RenderOnce, StatefulInteractiveElement as _,\n    StyleRefinement, Styled, TitlebarOptions, Window, WindowControlArea, div,\n    prelude::FluentBuilder as _, px,\n};\nuse smallvec::SmallVec;\n\npub const TITLE_BAR_HEIGHT: Pixels = px(34.);\n#[cfg(target_os = \"macos\")]\nconst TITLE_BAR_LEFT_PADDING: Pixels = px(80.);\n#[cfg(not(target_os = \"macos\"))]\nconst TITLE_BAR_LEFT_PADDING: Pixels = px(12.);\n\n/// TitleBar used to customize the appearance of the title bar.\n///\n/// We can put some elements inside the title bar.\n#[derive(IntoElement)]\npub struct TitleBar {\n    style: StyleRefinement,\n    children: SmallVec<[AnyElement; 1]>,\n    on_close_window: Option<Rc<Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>>>,\n}\n\nimpl TitleBar {\n    /// Create a new TitleBar.\n    pub fn new() -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            children: SmallVec::new(),\n            on_close_window: None,\n        }\n    }\n\n    /// Returns the default title bar options for compatible with the [`crate::TitleBar`].\n    pub fn title_bar_options() -> TitlebarOptions {\n        TitlebarOptions {\n            title: None,\n            appears_transparent: true,\n            traffic_light_position: Some(gpui::point(px(9.0), px(9.0))),\n        }\n    }\n\n    /// Add custom for close window event, default is None, then click X button will call `window.remove_window()`.\n    /// Linux only, this will do nothing on other platforms.\n    pub fn on_close_window(\n        mut self,\n        f: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,\n    ) -> Self {\n        if cfg!(target_os = \"linux\") {\n            self.on_close_window = Some(Rc::new(Box::new(f)));\n        }\n        self\n    }\n}\n\n// The Windows control buttons have a fixed width of 35px.\n//\n// We don't need implementation the click event for the control buttons.\n// If user clicked in the bounds, the window event will be triggered.\n#[derive(IntoElement, Clone)]\nenum ControlIcon {\n    Minimize,\n    Restore,\n    Maximize,\n    Close {\n        on_close_window: Option<Rc<Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>>>,\n    },\n}\n\nimpl ControlIcon {\n    fn minimize() -> Self {\n        Self::Minimize\n    }\n\n    fn restore() -> Self {\n        Self::Restore\n    }\n\n    fn maximize() -> Self {\n        Self::Maximize\n    }\n\n    fn close(on_close_window: Option<Rc<Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>>>) -> Self {\n        Self::Close { on_close_window }\n    }\n\n    fn id(&self) -> &'static str {\n        match self {\n            Self::Minimize => \"minimize\",\n            Self::Restore => \"restore\",\n            Self::Maximize => \"maximize\",\n            Self::Close { .. } => \"close\",\n        }\n    }\n\n    fn icon(&self) -> IconName {\n        match self {\n            Self::Minimize => IconName::WindowMinimize,\n            Self::Restore => IconName::WindowRestore,\n            Self::Maximize => IconName::WindowMaximize,\n            Self::Close { .. } => IconName::WindowClose,\n        }\n    }\n\n    fn window_control_area(&self) -> WindowControlArea {\n        match self {\n            Self::Minimize => WindowControlArea::Min,\n            Self::Restore | Self::Maximize => WindowControlArea::Max,\n            Self::Close { .. } => WindowControlArea::Close,\n        }\n    }\n\n    fn is_close(&self) -> bool {\n        matches!(self, Self::Close { .. })\n    }\n\n    #[inline]\n    fn hover_fg(&self, cx: &App) -> Hsla {\n        if self.is_close() {\n            cx.theme().danger_foreground\n        } else {\n            cx.theme().secondary_foreground\n        }\n    }\n\n    #[inline]\n    fn hover_bg(&self, cx: &App) -> Hsla {\n        if self.is_close() {\n            cx.theme().danger\n        } else {\n            cx.theme().secondary_hover\n        }\n    }\n\n    #[inline]\n    fn active_bg(&self, cx: &mut App) -> Hsla {\n        if self.is_close() {\n            cx.theme().danger_active\n        } else {\n            cx.theme().secondary_active\n        }\n    }\n}\n\nimpl RenderOnce for ControlIcon {\n    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {\n        let is_linux = cfg!(target_os = \"linux\");\n        let is_windows = cfg!(target_os = \"windows\");\n        let hover_fg = self.hover_fg(cx);\n        let hover_bg = self.hover_bg(cx);\n        let active_bg = self.active_bg(cx);\n        let icon = self.clone();\n        let on_close_window = match &self {\n            ControlIcon::Close { on_close_window } => on_close_window.clone(),\n            _ => None,\n        };\n\n        div()\n            .id(self.id())\n            .flex()\n            .w(TITLE_BAR_HEIGHT)\n            .h_full()\n            .flex_shrink_0()\n            .justify_center()\n            .content_center()\n            .items_center()\n            .text_color(cx.theme().foreground)\n            .hover(|style| style.bg(hover_bg).text_color(hover_fg))\n            .active(|style| style.bg(active_bg).text_color(hover_fg))\n            .when(is_windows, |this| {\n                this.window_control_area(self.window_control_area())\n            })\n            .when(is_linux, |this| {\n                this.on_mouse_down(MouseButton::Left, move |_, window, cx| {\n                    window.prevent_default();\n                    cx.stop_propagation();\n                })\n                .on_click(move |_, window, cx| {\n                    cx.stop_propagation();\n                    match icon {\n                        Self::Minimize => window.minimize_window(),\n                        Self::Restore | Self::Maximize => window.zoom_window(),\n                        Self::Close { .. } => {\n                            if let Some(f) = on_close_window.clone() {\n                                f(&ClickEvent::default(), window, cx);\n                            } else {\n                                window.remove_window();\n                            }\n                        }\n                    }\n                })\n            })\n            .child(Icon::new(self.icon()).small())\n    }\n}\n\n#[derive(IntoElement)]\nstruct WindowControls {\n    on_close_window: Option<Rc<Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>>>,\n}\n\nimpl RenderOnce for WindowControls {\n    fn render(self, window: &mut Window, _: &mut App) -> impl IntoElement {\n        if cfg!(target_os = \"macos\") || cfg!(target_family = \"wasm\") {\n            return div().id(\"window-controls\");\n        }\n\n        h_flex()\n            .id(\"window-controls\")\n            .items_center()\n            .flex_shrink_0()\n            .h_full()\n            .child(ControlIcon::minimize())\n            .child(if window.is_maximized() {\n                ControlIcon::restore()\n            } else {\n                ControlIcon::maximize()\n            })\n            .child(ControlIcon::close(self.on_close_window))\n    }\n}\n\nimpl Styled for TitleBar {\n    fn style(&mut self) -> &mut gpui::StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl ParentElement for TitleBar {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nstruct TitleBarState {\n    should_move: bool,\n}\n\n// TODO: Remove this when GPUI has released v0.2.3\nimpl Render for TitleBarState {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        div()\n    }\n}\n\nimpl RenderOnce for TitleBar {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let is_client_decorated = matches!(window.window_decorations(), Decorations::Client { .. });\n        let is_web = cfg!(target_family = \"wasm\");\n        let is_linux = cfg!(target_os = \"linux\");\n        let is_macos = cfg!(target_os = \"macos\");\n\n        let state = window.use_state(cx, |_, _| TitleBarState { should_move: false });\n\n        div().flex_shrink_0().child(\n            div()\n                .id(\"title-bar\")\n                .flex()\n                .flex_row()\n                .items_center()\n                .justify_between()\n                .h(TITLE_BAR_HEIGHT)\n                .pl(TITLE_BAR_LEFT_PADDING)\n                .border_b_1()\n                .border_color(cx.theme().title_bar_border)\n                .bg(cx.theme().title_bar)\n                .refine_style(&self.style)\n                .when(is_linux, |this| {\n                    this.on_double_click(|_, window, _| window.zoom_window())\n                })\n                .when(is_macos, |this| {\n                    this.on_double_click(|_, window, _| window.titlebar_double_click())\n                })\n                .on_mouse_down_out(window.listener_for(&state, |state, _, _, _| {\n                    state.should_move = false;\n                }))\n                .on_mouse_down(\n                    MouseButton::Left,\n                    window.listener_for(&state, |state, _, _, _| {\n                        state.should_move = true;\n                    }),\n                )\n                .on_mouse_up(\n                    MouseButton::Left,\n                    window.listener_for(&state, |state, _, _, _| {\n                        state.should_move = false;\n                    }),\n                )\n                .on_mouse_move(window.listener_for(&state, |state, _, window, _| {\n                    if state.should_move {\n                        state.should_move = false;\n                        window.start_window_move();\n                    }\n                }))\n                .child(\n                    h_flex()\n                        .id(\"bar\")\n                        .h_full()\n                        .justify_between()\n                        .flex_shrink_0()\n                        .flex_1()\n                        .when(!is_web, |this| {\n                            this.window_control_area(WindowControlArea::Drag)\n                                .when(window.is_fullscreen(), |this| this.pl_3())\n                                .when(is_linux && is_client_decorated, |this| {\n                                    this.child(\n                                        div()\n                                            .top_0()\n                                            .left_0()\n                                            .absolute()\n                                            .size_full()\n                                            .h_full()\n                                            .on_mouse_down(\n                                                MouseButton::Right,\n                                                move |ev, window, _| {\n                                                    window.show_window_menu(ev.position)\n                                                },\n                                            ),\n                                    )\n                                })\n                        })\n                        .children(self.children),\n                )\n                .child(WindowControls {\n                    on_close_window: self.on_close_window,\n                }),\n        )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/tooltip.rs",
    "content": "use gpui::{\n    div, prelude::FluentBuilder, px, Action, AnyElement, AnyView, App, AppContext, Context,\n    IntoElement, ParentElement, Render, SharedString, StyleRefinement, Styled, Window,\n};\n\nuse crate::{h_flex, kbd::Kbd, text::Text, ActiveTheme, StyledExt};\n\nenum TooltipContext {\n    Text(Text),\n    Element(Box<dyn Fn(&mut Window, &mut App) -> AnyElement>),\n}\n\n/// A Tooltip element that can display text or custom content,\n/// with optional key binding information.\npub struct Tooltip {\n    style: StyleRefinement,\n    content: TooltipContext,\n    key_binding: Option<Kbd>,\n    action: Option<(Box<dyn Action>, Option<SharedString>)>,\n}\n\nimpl Tooltip {\n    /// Create a Tooltip with a text content.\n    pub fn new(text: impl Into<Text>) -> Self {\n        Self {\n            style: StyleRefinement::default(),\n            content: TooltipContext::Text(text.into()),\n            key_binding: None,\n            action: None,\n        }\n    }\n\n    /// Create a Tooltip with a custom element.\n    pub fn element<E, F>(builder: F) -> Self\n    where\n        E: IntoElement,\n        F: Fn(&mut Window, &mut App) -> E + 'static,\n    {\n        Self {\n            style: StyleRefinement::default(),\n            key_binding: None,\n            action: None,\n            content: TooltipContext::Element(Box::new(move |window, cx| {\n                builder(window, cx).into_any_element()\n            })),\n        }\n    }\n\n    /// Set Action to display key binding information for the tooltip if it exists.\n    pub fn action(mut self, action: &dyn Action, context: Option<&str>) -> Self {\n        self.action = Some((action.boxed_clone(), context.map(SharedString::new)));\n        self\n    }\n\n    /// Set KeyBinding information for the tooltip.\n    pub fn key_binding(mut self, key_binding: Option<Kbd>) -> Self {\n        self.key_binding = key_binding;\n        self\n    }\n\n    /// Build the tooltip and return it as an `AnyView`.\n    pub fn build(self, _: &mut Window, cx: &mut App) -> AnyView {\n        cx.new(|_| self).into()\n    }\n}\n\nimpl FluentBuilder for Tooltip {}\nimpl Styled for Tooltip {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\nimpl Render for Tooltip {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let key_binding = if let Some(key_binding) = &self.key_binding {\n            Some(key_binding.clone())\n        } else {\n            if let Some((action, context)) = &self.action {\n                Kbd::binding_for_action(\n                    action.as_ref(),\n                    context.as_ref().map(|s| s.as_ref()),\n                    window,\n                )\n            } else {\n                None\n            }\n        };\n\n        div().child(\n            // Wrap in a child, to ensure the left margin is applied to the tooltip\n            h_flex()\n                .font_family(cx.theme().font_family.clone())\n                .m_3()\n                .bg(cx.theme().popover)\n                .text_color(cx.theme().popover_foreground)\n                .bg(cx.theme().popover)\n                .border_1()\n                .border_color(cx.theme().border)\n                .shadow_md()\n                .rounded(px(6.))\n                .justify_between()\n                .py_0p5()\n                .px_2()\n                .text_sm()\n                .gap_3()\n                .refine_style(&self.style)\n                .map(|this| {\n                    this.child(div().map(|this| match self.content {\n                        TooltipContext::Text(ref text) => this.child(text.clone()),\n                        TooltipContext::Element(ref builder) => this.child(builder(window, cx)),\n                    }))\n                })\n                .when_some(key_binding, |this, kbd| {\n                    this.child(\n                        div()\n                            .text_xs()\n                            .flex_shrink_0()\n                            .text_color(cx.theme().muted_foreground)\n                            .child(kbd.appearance(false)),\n                    )\n                }),\n        )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/tree.rs",
    "content": "use std::{cell::RefCell, ops::Range, rc::Rc};\n\nuse gpui::{\n    App, Context, ElementId, Entity, FocusHandle, InteractiveElement as _, IntoElement, KeyBinding,\n    ListSizingBehavior, MouseButton, ParentElement, Render, RenderOnce, SharedString,\n    StyleRefinement, Styled, UniformListScrollHandle, Window, div, prelude::FluentBuilder as _,\n    uniform_list,\n};\n\nuse crate::{\n    StyledExt,\n    actions::{Confirm, SelectDown, SelectLeft, SelectRight, SelectUp},\n    list::ListItem,\n    scroll::ScrollableElement,\n};\n\nconst CONTEXT: &str = \"Tree\";\npub(crate) fn init(cx: &mut App) {\n    cx.bind_keys([\n        KeyBinding::new(\"up\", SelectUp, Some(CONTEXT)),\n        KeyBinding::new(\"down\", SelectDown, Some(CONTEXT)),\n        KeyBinding::new(\"left\", SelectLeft, Some(CONTEXT)),\n        KeyBinding::new(\"right\", SelectRight, Some(CONTEXT)),\n    ]);\n}\n\n/// Create a [`Tree`].\n///\n/// # Arguments\n///\n/// * `state` - The shared state managing the tree items.\n/// * `render_item` - A closure to render each tree item.\n///\n/// ```ignore\n/// let state = cx.new(|_| {\n///     TreeState::new().items(vec![\n///         TreeItem::new(\"src\")\n///             .child(TreeItem::new(\"lib.rs\"),\n///         TreeItem::new(\"Cargo.toml\"),\n///         TreeItem::new(\"README.md\"),\n///     ])\n/// });\n///\n/// tree(&state, |ix, entry, selected, window, cx| {\n///     let item = entry.item();\n///     ListItem::new(ix).pl(px(16.) * entry.depth()).child(item.label.clone())\n/// })\n/// ```\npub fn tree<R>(state: &Entity<TreeState>, render_item: R) -> Tree\nwhere\n    R: Fn(usize, &TreeEntry, bool, &mut Window, &mut App) -> ListItem + 'static,\n{\n    Tree::new(state, render_item)\n}\n\nstruct TreeItemState {\n    expanded: bool,\n    disabled: bool,\n}\n\n/// A tree item with a label, children, and an expanded state.\n#[derive(Clone)]\npub struct TreeItem {\n    pub id: SharedString,\n    pub label: SharedString,\n    pub children: Vec<TreeItem>,\n    state: Rc<RefCell<TreeItemState>>,\n}\n\n/// A flat representation of a tree item with its depth.\n#[derive(Clone)]\npub struct TreeEntry {\n    item: TreeItem,\n    depth: usize,\n}\n\nimpl TreeEntry {\n    /// Get the source tree item.\n    #[inline]\n    pub fn item(&self) -> &TreeItem {\n        &self.item\n    }\n\n    /// The depth of this item in the tree.\n    #[inline]\n    pub fn depth(&self) -> usize {\n        self.depth\n    }\n\n    #[inline]\n    fn is_root(&self) -> bool {\n        self.depth == 0\n    }\n\n    /// Whether this item is a folder (has children).\n    #[inline]\n    pub fn is_folder(&self) -> bool {\n        self.item.is_folder()\n    }\n\n    /// Return true if the item is expanded.\n    #[inline]\n    pub fn is_expanded(&self) -> bool {\n        self.item.is_expanded()\n    }\n\n    #[inline]\n    pub fn is_disabled(&self) -> bool {\n        self.item.is_disabled()\n    }\n}\n\nimpl TreeItem {\n    /// Create a new tree item with the given label.\n    ///\n    /// - The `id` for you to uniquely identify this item, then later you can use it for selection or other purposes.\n    /// - The `label` is the text to display for this item.\n    ///\n    /// For example, the `id` is the full file path, and the `label` is the file name.\n    ///\n    /// ```ignore\n    /// TreeItem::new(\"src/ui/button.rs\", \"button.rs\")\n    /// ```\n    pub fn new(id: impl Into<SharedString>, label: impl Into<SharedString>) -> Self {\n        Self {\n            id: id.into(),\n            label: label.into(),\n            children: Vec::new(),\n            state: Rc::new(RefCell::new(TreeItemState {\n                expanded: false,\n                disabled: false,\n            })),\n        }\n    }\n\n    /// Add a child item to this tree item.\n    pub fn child(mut self, child: TreeItem) -> Self {\n        self.children.push(child);\n        self\n    }\n\n    /// Add multiple child items to this tree item.\n    pub fn children(mut self, children: impl IntoIterator<Item = TreeItem>) -> Self {\n        self.children.extend(children);\n        self\n    }\n\n    /// Set expanded state for this tree item.\n    pub fn expanded(self, expanded: bool) -> Self {\n        self.state.borrow_mut().expanded = expanded;\n        self\n    }\n\n    /// Set disabled state for this tree item.\n    pub fn disabled(self, disabled: bool) -> Self {\n        self.state.borrow_mut().disabled = disabled;\n        self\n    }\n\n    /// Whether this item is a folder (has children).\n    #[inline]\n    pub fn is_folder(&self) -> bool {\n        self.children.len() > 0\n    }\n\n    /// Return true if the item is disabled.\n    pub fn is_disabled(&self) -> bool {\n        self.state.borrow().disabled\n    }\n\n    /// Return true if the item is expanded.\n    #[inline]\n    pub fn is_expanded(&self) -> bool {\n        self.state.borrow().expanded\n    }\n\n    fn find_ancestors(&self, target_id: &SharedString) -> Option<Vec<TreeItem>> {\n        if self.id == *target_id {\n            return Some(vec![]);\n        }\n\n        for child in &self.children {\n            if let Some(mut path) = child.find_ancestors(target_id) {\n                path.push(self.clone());\n                return Some(path);\n            }\n        }\n\n        None\n    }\n}\n\n/// State for managing tree items.\npub struct TreeState {\n    focus_handle: FocusHandle,\n    entries: Vec<TreeEntry>,\n    scroll_handle: UniformListScrollHandle,\n    selected_ix: Option<usize>,\n    render_item: Rc<dyn Fn(usize, &TreeEntry, bool, &mut Window, &mut App) -> ListItem>,\n}\n\nimpl TreeState {\n    /// Create a new empty tree state.\n    pub fn new(cx: &mut App) -> Self {\n        Self {\n            selected_ix: None,\n            focus_handle: cx.focus_handle(),\n            scroll_handle: UniformListScrollHandle::default(),\n            entries: Vec::new(),\n            render_item: Rc::new(|_, _, _, _, _| ListItem::new(0)),\n        }\n    }\n\n    /// Set the tree items.\n    pub fn items(mut self, items: impl Into<Vec<TreeItem>>) -> Self {\n        let items = items.into();\n        self.entries.clear();\n        for item in items.into_iter() {\n            self.add_entry(item, 0);\n        }\n        self\n    }\n\n    /// Set the tree items.\n    pub fn set_items(&mut self, items: impl Into<Vec<TreeItem>>, cx: &mut Context<Self>) {\n        let items = items.into();\n        self.entries.clear();\n        for item in items.into_iter() {\n            self.add_entry(item, 0);\n        }\n        self.selected_ix = None;\n        cx.notify();\n    }\n\n    /// Get the currently selected index, if any.\n    pub fn selected_index(&self) -> Option<usize> {\n        self.selected_ix\n    }\n\n    /// Set the selected index, or `None` to clear selection.\n    pub fn set_selected_index(&mut self, ix: Option<usize>, cx: &mut Context<Self>) {\n        self.selected_ix = ix;\n        cx.notify();\n    }\n\n    /// Set the selected index by tree item, or `None` to clear selection.\n    pub fn set_selected_item(&mut self, item: Option<&TreeItem>, cx: &mut Context<Self>) {\n        if let Some(item) = item {\n            let ix = self\n                .entries\n                .iter()\n                .position(|entry| entry.item.id == item.id);\n            if ix.is_some() {\n                self.selected_ix = ix;\n            } else {\n                self.expand_ancestors(item.id.clone());\n                self.selected_ix = self\n                    .entries\n                    .iter()\n                    .position(|entry| entry.item.id == item.id);\n            }\n        } else {\n            self.selected_ix = None;\n        }\n        cx.notify();\n    }\n\n    /// Get the currently selected tree item, if any.\n    pub fn selected_item(&self) -> Option<&TreeItem> {\n        self.selected_ix\n            .and_then(|ix| self.entries.get(ix).map(|entry| &entry.item))\n    }\n\n    pub fn scroll_to_item(&mut self, ix: usize, strategy: gpui::ScrollStrategy) {\n        self.scroll_handle.scroll_to_item(ix, strategy);\n    }\n\n    /// Get the currently selected entry, if any.\n    pub fn selected_entry(&self) -> Option<&TreeEntry> {\n        self.selected_ix.and_then(|ix| self.entries.get(ix))\n    }\n\n    fn expand_ancestors(&mut self, target_id: SharedString) {\n        let mut ancestors = Vec::new();\n\n        for entry in &self.entries {\n            if let Some(found_ancestors) = entry.item.find_ancestors(&target_id) {\n                ancestors = found_ancestors;\n                break;\n            }\n        }\n\n        if ancestors.is_empty() {\n            return;\n        }\n\n        for ancestor in ancestors {\n            ancestor.state.borrow_mut().expanded = true;\n        }\n\n        self.rebuild_entries();\n    }\n\n    fn add_entry(&mut self, item: TreeItem, depth: usize) {\n        self.entries.push(TreeEntry {\n            item: item.clone(),\n            depth,\n        });\n        if item.is_expanded() {\n            for child in &item.children {\n                self.add_entry(child.clone(), depth + 1);\n            }\n        }\n    }\n\n    fn toggle_expand(&mut self, ix: usize) {\n        let Some(entry) = self.entries.get_mut(ix) else {\n            return;\n        };\n        if !entry.is_folder() {\n            return;\n        }\n\n        entry.item.state.borrow_mut().expanded = !entry.is_expanded();\n        self.rebuild_entries();\n    }\n\n    fn rebuild_entries(&mut self) {\n        let root_items: Vec<TreeItem> = self\n            .entries\n            .iter()\n            .filter(|e| e.is_root())\n            .map(|e| e.item.clone())\n            .collect();\n        self.entries.clear();\n        for item in root_items.into_iter() {\n            self.add_entry(item, 0);\n        }\n    }\n\n    pub fn focus(&mut self, window: &mut Window, cx: &mut App) {\n        self.focus_handle.focus(window, cx);\n    }\n\n    fn on_action_confirm(&mut self, _: &Confirm, _: &mut Window, cx: &mut Context<Self>) {\n        if let Some(selected_ix) = self.selected_ix {\n            if let Some(entry) = self.entries.get(selected_ix) {\n                if entry.is_folder() {\n                    self.toggle_expand(selected_ix);\n                    cx.notify();\n                }\n            }\n        }\n    }\n\n    fn on_action_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {\n        if let Some(selected_ix) = self.selected_ix {\n            if let Some(entry) = self.entries.get(selected_ix) {\n                if entry.is_folder() && entry.is_expanded() {\n                    self.toggle_expand(selected_ix);\n                    cx.notify();\n                }\n            }\n        }\n    }\n\n    fn on_action_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {\n        if let Some(selected_ix) = self.selected_ix {\n            if let Some(entry) = self.entries.get(selected_ix) {\n                if entry.is_folder() && !entry.is_expanded() {\n                    self.toggle_expand(selected_ix);\n                    cx.notify();\n                }\n            }\n        }\n    }\n\n    fn on_action_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {\n        let mut selected_ix = self.selected_ix.unwrap_or(0);\n\n        if selected_ix > 0 {\n            selected_ix = selected_ix - 1;\n        } else {\n            selected_ix = self.entries.len().saturating_sub(1);\n        }\n\n        self.selected_ix = Some(selected_ix);\n        self.scroll_handle\n            .scroll_to_item(selected_ix, gpui::ScrollStrategy::Top);\n        cx.notify();\n    }\n\n    fn on_action_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {\n        let mut selected_ix = self.selected_ix.unwrap_or(0);\n        if selected_ix + 1 < self.entries.len() {\n            selected_ix = selected_ix + 1;\n        } else {\n            selected_ix = 0;\n        }\n\n        self.selected_ix = Some(selected_ix);\n        self.scroll_handle\n            .scroll_to_item(selected_ix, gpui::ScrollStrategy::Bottom);\n        cx.notify();\n    }\n\n    fn on_entry_click(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Self>) {\n        self.selected_ix = Some(ix);\n        self.toggle_expand(ix);\n        cx.notify();\n    }\n}\n\nimpl Render for TreeState {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let render_item = self.render_item.clone();\n\n        div().id(\"tree-state\").size_full().relative().child(\n            uniform_list(\"entries\", self.entries.len(), {\n                cx.processor(move |state, visible_range: Range<usize>, window, cx| {\n                    let mut items = Vec::with_capacity(visible_range.len());\n                    for ix in visible_range {\n                        let entry = &state.entries[ix];\n                        let selected = Some(ix) == state.selected_ix;\n                        let item = (render_item)(ix, entry, selected, window, cx);\n\n                        let el = div()\n                            .id(ix)\n                            .child(item.disabled(entry.item().is_disabled()).selected(selected))\n                            .when(!entry.item().is_disabled(), |this| {\n                                this.on_mouse_down(\n                                    MouseButton::Left,\n                                    cx.listener({\n                                        move |this, _, window, cx| {\n                                            this.on_entry_click(ix, window, cx);\n                                        }\n                                    }),\n                                )\n                            });\n\n                        items.push(el)\n                    }\n\n                    items\n                })\n            })\n            .flex_grow()\n            .size_full()\n            .track_scroll(&self.scroll_handle)\n            .with_sizing_behavior(ListSizingBehavior::Auto)\n            .into_any_element(),\n        )\n    }\n}\n\n/// A tree view element that displays hierarchical data.\n#[derive(IntoElement)]\npub struct Tree {\n    id: ElementId,\n    state: Entity<TreeState>,\n    style: StyleRefinement,\n    render_item: Rc<dyn Fn(usize, &TreeEntry, bool, &mut Window, &mut App) -> ListItem>,\n}\n\nimpl Tree {\n    pub fn new<R>(state: &Entity<TreeState>, render_item: R) -> Self\n    where\n        R: Fn(usize, &TreeEntry, bool, &mut Window, &mut App) -> ListItem + 'static,\n    {\n        Self {\n            id: ElementId::Name(format!(\"tree-{}\", state.entity_id()).into()),\n            state: state.clone(),\n            style: StyleRefinement::default(),\n            render_item: Rc::new(move |ix, item, selected, window, app| {\n                render_item(ix, item, selected, window, app)\n            }),\n        }\n    }\n}\n\nimpl Styled for Tree {\n    fn style(&mut self) -> &mut StyleRefinement {\n        &mut self.style\n    }\n}\n\nimpl RenderOnce for Tree {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let focus_handle = self.state.read(cx).focus_handle.clone();\n        let scroll_handle = self.state.read(cx).scroll_handle.clone();\n\n        self.state\n            .update(cx, |state, _| state.render_item = self.render_item);\n\n        div()\n            .id(self.id)\n            .key_context(CONTEXT)\n            .track_focus(&focus_handle)\n            .on_action(window.listener_for(&self.state, TreeState::on_action_confirm))\n            .on_action(window.listener_for(&self.state, TreeState::on_action_left))\n            .on_action(window.listener_for(&self.state, TreeState::on_action_right))\n            .on_action(window.listener_for(&self.state, TreeState::on_action_up))\n            .on_action(window.listener_for(&self.state, TreeState::on_action_down))\n            .size_full()\n            .child(self.state)\n            .refine_style(&self.style)\n            .vertical_scrollbar(&scroll_handle)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use indoc::indoc;\n\n    use super::TreeState;\n    use gpui::AppContext as _;\n\n    fn assert_entries(entries: &Vec<super::TreeEntry>, expected: &str) {\n        let actual: Vec<String> = entries\n            .iter()\n            .map(|e| {\n                let mut s = String::new();\n                s.push_str(&\"    \".repeat(e.depth));\n                s.push_str(e.item().label.as_str());\n                s\n            })\n            .collect();\n        let actual = actual.join(\"\\n\");\n        assert_eq!(actual.trim(), expected.trim());\n    }\n\n    #[gpui::test]\n    fn test_tree_entry(cx: &mut gpui::TestAppContext) {\n        use super::TreeItem;\n\n        let items = vec![\n            TreeItem::new(\"src\", \"src\")\n                .expanded(true)\n                .child(\n                    TreeItem::new(\"src/ui\", \"ui\")\n                        .expanded(true)\n                        .child(TreeItem::new(\"src/ui/button.rs\", \"button.rs\"))\n                        .child(TreeItem::new(\"src/ui/icon.rs\", \"icon.rs\"))\n                        .child(TreeItem::new(\"src/ui/mod.rs\", \"mod.rs\")),\n                )\n                .child(TreeItem::new(\"src/lib.rs\", \"lib.rs\")),\n            TreeItem::new(\"Cargo.toml\", \"Cargo.toml\"),\n            TreeItem::new(\"Cargo.lock\", \"Cargo.lock\").disabled(true),\n            TreeItem::new(\"README.md\", \"README.md\"),\n        ];\n\n        let state = cx.new(|cx| TreeState::new(cx).items(items));\n        state.update(cx, |state, _| {\n            assert_entries(\n                &state.entries,\n                indoc! {\n                    r#\"\n                src\n                    ui\n                        button.rs\n                        icon.rs\n                        mod.rs\n                    lib.rs\n                Cargo.toml\n                Cargo.lock\n                README.md\n                \"#\n                },\n            );\n\n            let entry = state.entries.get(0).unwrap();\n            assert_eq!(entry.depth(), 0);\n            assert_eq!(entry.is_root(), true);\n            assert_eq!(entry.is_folder(), true);\n            assert_eq!(entry.is_expanded(), true);\n\n            let entry = state.entries.get(1).unwrap();\n            assert_eq!(entry.depth(), 1);\n            assert_eq!(entry.is_root(), false);\n            assert_eq!(entry.is_folder(), true);\n            assert_eq!(entry.is_expanded(), true);\n            assert_eq!(entry.item().label.as_str(), \"ui\");\n\n            state.toggle_expand(1);\n            let entry = state.entries.get(1).unwrap();\n            assert_eq!(entry.is_expanded(), false);\n            assert_entries(\n                &state.entries,\n                indoc! {\n                    r#\"\n                src\n                    ui\n                    lib.rs\n                Cargo.toml\n                Cargo.lock\n                README.md\n                \"#\n                },\n            );\n        })\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/virtual_list.rs",
    "content": "//! Virtual List for render a large number of differently sized rows/columns.\n//!\n//! > NOTE: This must ensure each column width or row height.\n//!\n//! Only visible range are rendered for performance reasons.\n//!\n//! Inspired by `gpui::uniform_list`.\n//! https://github.com/zed-industries/zed/blob/0ae1603610ab6b265bdfbee7b8dbc23c5ab06edc/crates/gpui/src/elements/uniform_list.rs\n//!\n//! Unlike the `uniform_list`, the each item can have different size.\n//!\n//! This is useful for more complex layout, for example, a table with different row height.\nuse std::{\n    cell::RefCell,\n    cmp,\n    ops::{Deref, Range},\n    rc::Rc,\n};\n\nuse gpui::{\n    Along, AnyElement, App, AvailableSpace, Axis, Bounds, ContentMask, Context,\n    DeferredScrollToItem, Div, Element, ElementId, Entity, GlobalElementId, Half, Hitbox,\n    InteractiveElement, IntoElement, IsZero as _, ListSizingBehavior, Pixels, Point, Render,\n    ScrollHandle, ScrollStrategy, Size, Stateful, StatefulInteractiveElement, StyleRefinement,\n    Styled, Window, div, point, px, size,\n};\nuse smallvec::SmallVec;\n\nuse crate::{AxisExt, scroll::ScrollbarHandle};\n\nstruct VirtualListScrollHandleState {\n    axis: Axis,\n    items_count: usize,\n    pub deferred_scroll_to_item: Option<DeferredScrollToItem>,\n}\n\n/// A scroll handle for [`VirtualList`].\n///\n/// See also [`ScrollHandle`].\n#[derive(Clone)]\npub struct VirtualListScrollHandle {\n    state: Rc<RefCell<VirtualListScrollHandleState>>,\n    base_handle: ScrollHandle,\n}\n\nimpl From<ScrollHandle> for VirtualListScrollHandle {\n    fn from(handle: ScrollHandle) -> Self {\n        let mut this = VirtualListScrollHandle::new();\n        this.base_handle = handle;\n        this\n    }\n}\n\nimpl AsRef<ScrollHandle> for VirtualListScrollHandle {\n    fn as_ref(&self) -> &ScrollHandle {\n        &self.base_handle\n    }\n}\n\nimpl ScrollbarHandle for VirtualListScrollHandle {\n    fn offset(&self) -> Point<Pixels> {\n        self.base_handle.offset()\n    }\n\n    fn set_offset(&self, offset: Point<Pixels>) {\n        self.base_handle.set_offset(offset);\n    }\n\n    fn content_size(&self) -> Size<Pixels> {\n        self.base_handle.content_size()\n    }\n}\n\nimpl Deref for VirtualListScrollHandle {\n    type Target = ScrollHandle;\n\n    fn deref(&self) -> &Self::Target {\n        &self.base_handle\n    }\n}\n\nimpl VirtualListScrollHandle {\n    /// Create a new VirtualListScrollHandle.\n    pub fn new() -> Self {\n        VirtualListScrollHandle {\n            state: Rc::new(RefCell::new(VirtualListScrollHandleState {\n                axis: Axis::Vertical,\n                items_count: 0,\n                deferred_scroll_to_item: None,\n            })),\n            base_handle: ScrollHandle::default(),\n        }\n    }\n\n    /// Get the base scroll handle.\n    pub fn base_handle(&self) -> &ScrollHandle {\n        &self.base_handle\n    }\n\n    /// Scroll to the item at the given index.\n    pub fn scroll_to_item(&self, ix: usize, strategy: ScrollStrategy) {\n        self.scroll_to_item_with_offset(ix, strategy, 0);\n    }\n\n    /// Scroll to the item at the given index, with an additional offset items.\n    fn scroll_to_item_with_offset(&self, ix: usize, strategy: ScrollStrategy, offset: usize) {\n        let mut state = self.state.borrow_mut();\n        state.deferred_scroll_to_item = Some(DeferredScrollToItem {\n            item_index: ix,\n            strategy,\n            offset,\n            scroll_strict: false,\n        });\n    }\n\n    /// Scrolls to the bottom of the list.\n    pub fn scroll_to_bottom(&self) {\n        let items_count = self.state.borrow().items_count;\n        self.scroll_to_item(items_count.saturating_sub(1), ScrollStrategy::Top);\n    }\n}\n\n/// Create a [`VirtualList`] in vertical direction.\n///\n/// This is like `uniform_list` in GPUI, but support two axis.\n///\n/// The `item_sizes` is the size of each column,\n/// only the `height` is used, `width` is ignored and VirtualList will measure the first item width.\n///\n/// See also [`h_virtual_list`]\n#[inline]\npub fn v_virtual_list<R, V>(\n    view: Entity<V>,\n    id: impl Into<ElementId>,\n    item_sizes: Rc<Vec<Size<Pixels>>>,\n    f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,\n) -> VirtualList\nwhere\n    R: IntoElement,\n    V: Render,\n{\n    virtual_list(view, id, Axis::Vertical, item_sizes, f)\n}\n\n/// Create a [`VirtualList`] in horizontal direction.\n///\n/// The `item_sizes` is the size of each column,\n/// only the `width` is used, `height` is ignored and VirtualList will measure the first item height.\n///\n/// See also [`v_virtual_list`]\n#[inline]\npub fn h_virtual_list<R, V>(\n    view: Entity<V>,\n    id: impl Into<ElementId>,\n    item_sizes: Rc<Vec<Size<Pixels>>>,\n    f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,\n) -> VirtualList\nwhere\n    R: IntoElement,\n    V: Render,\n{\n    virtual_list(view, id, Axis::Horizontal, item_sizes, f)\n}\n\npub(crate) fn virtual_list<R, V>(\n    view: Entity<V>,\n    id: impl Into<ElementId>,\n    axis: Axis,\n    item_sizes: Rc<Vec<Size<Pixels>>>,\n    f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,\n) -> VirtualList\nwhere\n    R: IntoElement,\n    V: Render,\n{\n    let id: ElementId = id.into();\n    let scroll_handle = VirtualListScrollHandle::new();\n    let render_range = move |visible_range, window: &mut Window, cx: &mut App| {\n        view.update(cx, |this, cx| {\n            f(this, visible_range, window, cx)\n                .into_iter()\n                .map(|component| component.into_any_element())\n                .collect()\n        })\n    };\n\n    VirtualList {\n        id: id.clone(),\n        axis,\n        base: div()\n            .id(id)\n            .size_full()\n            .overflow_scroll()\n            .track_scroll(&scroll_handle),\n        scroll_handle,\n        items_count: item_sizes.len(),\n        item_sizes,\n        render_items: Box::new(render_range),\n        sizing_behavior: ListSizingBehavior::default(),\n    }\n}\n\n/// VirtualList component for rendering a large number of differently sized items.\npub struct VirtualList {\n    id: ElementId,\n    axis: Axis,\n    base: Stateful<Div>,\n    scroll_handle: VirtualListScrollHandle,\n    items_count: usize,\n    item_sizes: Rc<Vec<Size<Pixels>>>,\n    render_items: Box<\n        dyn for<'a> Fn(Range<usize>, &'a mut Window, &'a mut App) -> SmallVec<[AnyElement; 64]>,\n    >,\n    sizing_behavior: ListSizingBehavior,\n}\n\nimpl Styled for VirtualList {\n    fn style(&mut self) -> &mut StyleRefinement {\n        self.base.style()\n    }\n}\n\nimpl VirtualList {\n    pub fn track_scroll(mut self, scroll_handle: &VirtualListScrollHandle) -> Self {\n        self.base = self.base.track_scroll(&scroll_handle);\n        self.scroll_handle = scroll_handle.clone();\n        self\n    }\n\n    /// Set the sizing behavior for the list.\n    pub fn with_sizing_behavior(mut self, behavior: ListSizingBehavior) -> Self {\n        self.sizing_behavior = behavior;\n        self\n    }\n\n    /// Specify for table.\n    ///\n    /// Table is special, because the `scroll_handle` is based on Table head (That is not a virtual list).\n    pub(crate) fn with_scroll_handle(mut self, scroll_handle: &VirtualListScrollHandle) -> Self {\n        self.base = div().id(self.id.clone()).size_full();\n        self.scroll_handle = scroll_handle.clone();\n        self\n    }\n\n    fn scroll_to_deferred_item(\n        &self,\n        scroll_offset: Point<Pixels>,\n        items_bounds: &[Bounds<Pixels>],\n        content_bounds: &Bounds<Pixels>,\n        scroll_to_item: DeferredScrollToItem,\n    ) -> Point<Pixels> {\n        let Some(bounds) = items_bounds\n            .get(scroll_to_item.item_index + scroll_to_item.offset)\n            .cloned()\n        else {\n            return scroll_offset;\n        };\n\n        let mut scroll_offset = scroll_offset;\n        match scroll_to_item.strategy {\n            ScrollStrategy::Center => {\n                if self.axis.is_vertical() {\n                    scroll_offset.y = content_bounds.top() + content_bounds.size.height.half()\n                        - bounds.top()\n                        - bounds.size.height.half()\n                } else {\n                    scroll_offset.x = content_bounds.left() + content_bounds.size.width.half()\n                        - bounds.left()\n                        - bounds.size.width.half()\n                }\n            }\n            _ => {\n                // Ref: https://github.com/zed-industries/zed/blob/0d145289e0867a8d5d63e5e1397a5ca69c9d49c3/crates/gpui/src/elements/div.rs#L3026\n                if self.axis.is_vertical() {\n                    if bounds.top() + scroll_offset.y < content_bounds.top() {\n                        scroll_offset.y = content_bounds.top() - bounds.top()\n                    } else if bounds.bottom() + scroll_offset.y > content_bounds.bottom() {\n                        scroll_offset.y = content_bounds.bottom() - bounds.bottom();\n                    }\n                } else {\n                    if bounds.left() + scroll_offset.x < content_bounds.left() {\n                        scroll_offset.x = content_bounds.left() - bounds.left();\n                    } else if bounds.right() + scroll_offset.x > content_bounds.right() {\n                        scroll_offset.x = content_bounds.right() - bounds.right();\n                    }\n                }\n            }\n        }\n        self.scroll_handle.set_offset(scroll_offset);\n        scroll_offset\n    }\n\n    /// Ref from: https://github.com/zed-industries/zed/blob/83f9f9d9e3f5914392cab9a09e3472711a1d7b38/crates/gpui/src/elements/uniform_list.rs#L660\n    fn measure_item(\n        &self,\n        list_width: Option<Pixels>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Size<Pixels> {\n        if self.items_count == 0 {\n            return Size::default();\n        }\n\n        let item_ix = 0;\n        let mut items = (self.render_items)(item_ix..item_ix + 1, window, cx);\n        let Some(mut item_to_measure) = items.pop() else {\n            return Size::default();\n        };\n        let available_space = size(\n            list_width.map_or(AvailableSpace::MinContent, |width| {\n                AvailableSpace::Definite(width)\n            }),\n            AvailableSpace::MinContent,\n        );\n        item_to_measure.layout_as_root(available_space, window, cx)\n    }\n}\n\n/// Frame state used by the [VirtualItem].\npub struct VirtualListFrameState {\n    /// Visible items to be painted.\n    items: SmallVec<[AnyElement; 32]>,\n    size_layout: ItemSizeLayout,\n}\n\n#[derive(Default, Clone)]\npub struct ItemSizeLayout {\n    items_sizes: Rc<Vec<Size<Pixels>>>,\n    content_size: Size<Pixels>,\n    sizes: Vec<Pixels>,\n    origins: Vec<Pixels>,\n    last_layout_bounds: Bounds<Pixels>,\n}\n\nimpl IntoElement for VirtualList {\n    type Element = Self;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\nimpl Element for VirtualList {\n    type RequestLayoutState = VirtualListFrameState;\n    type PrepaintState = Option<Hitbox>;\n\n    fn id(&self) -> Option<ElementId> {\n        Some(self.id.clone())\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&gpui::InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (gpui::LayoutId, Self::RequestLayoutState) {\n        let rem_size = window.rem_size();\n        let font_size = window.text_style().font_size.to_pixels(rem_size);\n        let mut size_layout = ItemSizeLayout::default();\n        let longest_item_size = self.measure_item(None, window, cx);\n\n        let layout_id = self.base.interactivity().request_layout(\n            global_id,\n            inspector_id,\n            window,\n            cx,\n            |style, window, cx| {\n                size_layout = window.with_element_state(\n                    global_id.unwrap(),\n                    |state: Option<ItemSizeLayout>, _window| {\n                        let mut state = state.unwrap_or(ItemSizeLayout::default());\n\n                        // Including the gap between items for calculate the item size\n                        let gap = style\n                            .gap\n                            .along(self.axis)\n                            .to_pixels(font_size.into(), rem_size);\n\n                        if state.items_sizes != self.item_sizes {\n                            state.items_sizes = self.item_sizes.clone();\n                            // Prepare each item's size by axis\n                            state.sizes = self\n                                .item_sizes\n                                .iter()\n                                .enumerate()\n                                .map(|(i, size)| {\n                                    let size = size.along(self.axis);\n                                    if i + 1 == self.items_count {\n                                        size\n                                    } else {\n                                        size + gap\n                                    }\n                                })\n                                .collect::<Vec<_>>();\n\n                            // Prepare each item's origin by axis\n                            state.origins = state\n                                .sizes\n                                .iter()\n                                .scan(px(0.), |cumulative, size| match self.axis {\n                                    Axis::Horizontal => {\n                                        let x = *cumulative;\n                                        *cumulative += *size;\n                                        Some(x)\n                                    }\n                                    Axis::Vertical => {\n                                        let y = *cumulative;\n                                        *cumulative += *size;\n                                        Some(y)\n                                    }\n                                })\n                                .collect::<Vec<_>>();\n\n                            state.content_size = if self.axis.is_horizontal() {\n                                Size {\n                                    width: px(state\n                                        .sizes\n                                        .iter()\n                                        .map(|size| size.as_f32())\n                                        .sum::<f32>()),\n                                    height: longest_item_size.height,\n                                }\n                            } else {\n                                Size {\n                                    width: longest_item_size.width,\n                                    height: px(state\n                                        .sizes\n                                        .iter()\n                                        .map(|size| size.as_f32())\n                                        .sum::<f32>()),\n                                }\n                            };\n                        }\n\n                        (state.clone(), state)\n                    },\n                );\n\n                let axis = self.axis;\n                let layout_id =\n                    match self.sizing_behavior {\n                        ListSizingBehavior::Infer => {\n                            window.with_text_style(style.text_style().cloned(), |window| {\n                                let size_layout = size_layout.clone();\n\n                                window.request_measured_layout(style, {\n                                    move |known_dimensions, available_space, _, _| {\n                                        let mut size = Size::default();\n                                        if axis.is_horizontal() {\n                                            size.width = known_dimensions.width.unwrap_or(\n                                                match available_space.width {\n                                                    AvailableSpace::Definite(x) => x,\n                                                    AvailableSpace::MinContent\n                                                    | AvailableSpace::MaxContent => {\n                                                        size_layout.content_size.width\n                                                    }\n                                                },\n                                            );\n                                            size.height = known_dimensions.width.unwrap_or(\n                                                match available_space.height {\n                                                    AvailableSpace::Definite(x) => x,\n                                                    AvailableSpace::MinContent\n                                                    | AvailableSpace::MaxContent => {\n                                                        size_layout.content_size.height\n                                                    }\n                                                },\n                                            );\n                                        } else {\n                                            size.width = known_dimensions.width.unwrap_or(\n                                                match available_space.width {\n                                                    AvailableSpace::Definite(x) => x,\n                                                    AvailableSpace::MinContent\n                                                    | AvailableSpace::MaxContent => {\n                                                        size_layout.content_size.width\n                                                    }\n                                                },\n                                            );\n                                            size.height = known_dimensions.height.unwrap_or(\n                                                match available_space.height {\n                                                    AvailableSpace::Definite(x) => x,\n                                                    AvailableSpace::MinContent\n                                                    | AvailableSpace::MaxContent => {\n                                                        size_layout.content_size.height\n                                                    }\n                                                },\n                                            );\n                                        }\n\n                                        size\n                                    }\n                                })\n                            })\n                        }\n                        ListSizingBehavior::Auto => window\n                            .with_text_style(style.text_style().cloned(), |window| {\n                                window.request_layout(style, None, cx)\n                            }),\n                    };\n\n                layout_id\n            },\n        );\n\n        (\n            layout_id,\n            VirtualListFrameState {\n                items: SmallVec::new(),\n                size_layout,\n            },\n        )\n    }\n\n    fn prepaint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&gpui::InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        layout: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::PrepaintState {\n        layout.size_layout.last_layout_bounds = bounds;\n\n        let style = self\n            .base\n            .interactivity()\n            .compute_style(global_id, None, window, cx);\n        let border_widths = style.border_widths.to_pixels(window.rem_size());\n        let paddings = style\n            .padding\n            .to_pixels(bounds.size.into(), window.rem_size());\n\n        let item_sizes = &layout.size_layout.sizes;\n        let item_origins = &layout.size_layout.origins;\n\n        let content_bounds = Bounds::from_corners(\n            bounds.origin\n                + point(\n                    border_widths.left + paddings.left,\n                    border_widths.top + paddings.top,\n                ),\n            bounds.bottom_right()\n                - point(\n                    border_widths.right + paddings.right,\n                    border_widths.bottom + paddings.bottom,\n                ),\n        );\n\n        // Update scroll_handle with the item bounds\n        let items_bounds = item_origins\n            .iter()\n            .enumerate()\n            .map(|(i, &origin)| {\n                let item_size = item_sizes[i];\n\n                Bounds {\n                    origin: match self.axis {\n                        Axis::Horizontal => point(content_bounds.left() + origin, px(0.)),\n                        Axis::Vertical => point(px(0.), content_bounds.top() + origin),\n                    },\n                    size: match self.axis {\n                        Axis::Horizontal => size(item_size, content_bounds.size.height),\n                        Axis::Vertical => size(content_bounds.size.width, item_size),\n                    },\n                }\n            })\n            .collect::<Vec<_>>();\n\n        let axis = self.axis;\n\n        let mut scroll_state = self.scroll_handle.state.borrow_mut();\n        scroll_state.axis = axis;\n        scroll_state.items_count = self.items_count;\n\n        let mut scroll_offset = self.scroll_handle.offset();\n        if let Some(scroll_to_item) = scroll_state.deferred_scroll_to_item.take() {\n            scroll_offset = self.scroll_to_deferred_item(\n                scroll_offset,\n                &items_bounds,\n                &content_bounds,\n                scroll_to_item,\n            );\n        }\n\n        scroll_offset = scroll_offset\n            .max(&point(\n                content_bounds.size.width - layout.size_layout.content_size.width,\n                content_bounds.size.height - layout.size_layout.content_size.height,\n            ))\n            .min(&point(px(0.), px(0.)));\n        if scroll_offset != self.scroll_handle.offset() {\n            self.scroll_handle.set_offset(scroll_offset);\n        }\n\n        self.base.interactivity().prepaint(\n            global_id,\n            inspector_id,\n            bounds,\n            layout.size_layout.content_size,\n            window,\n            cx,\n            |_style, _, hitbox, window, cx| {\n                if self.items_count > 0 {\n                    let min_scroll_offset = content_bounds.size.along(self.axis)\n                        - layout.size_layout.content_size.along(self.axis);\n\n                    let is_scrolled = !scroll_offset.along(self.axis).is_zero();\n                    if is_scrolled {\n                        match self.axis {\n                            Axis::Horizontal if scroll_offset.x < min_scroll_offset => {\n                                scroll_offset.x = min_scroll_offset;\n                                self.scroll_handle.set_offset(scroll_offset);\n                            }\n                            Axis::Vertical if scroll_offset.y < min_scroll_offset => {\n                                scroll_offset.y = min_scroll_offset;\n                                self.scroll_handle.set_offset(scroll_offset);\n                            }\n                            _ => {}\n                        }\n                    }\n\n                    let (first_visible_element_ix, last_visible_element_ix) = match self.axis {\n                        Axis::Horizontal => {\n                            let mut cumulative_size = px(0.);\n                            let mut first_visible_element_ix = 0;\n                            for (i, &size) in item_sizes.iter().enumerate() {\n                                cumulative_size += size;\n                                if cumulative_size > -(scroll_offset.x + paddings.left) {\n                                    first_visible_element_ix = i;\n                                    break;\n                                }\n                            }\n\n                            cumulative_size = px(0.);\n                            let mut last_visible_element_ix = 0;\n                            for (i, &size) in item_sizes.iter().enumerate() {\n                                cumulative_size += size;\n                                if cumulative_size > (-scroll_offset.x + content_bounds.size.width)\n                                {\n                                    last_visible_element_ix = i + 1;\n                                    break;\n                                }\n                            }\n                            if last_visible_element_ix == 0 {\n                                last_visible_element_ix = self.items_count;\n                            } else {\n                                last_visible_element_ix += 1;\n                            }\n                            (first_visible_element_ix, last_visible_element_ix)\n                        }\n                        Axis::Vertical => {\n                            let mut cumulative_size = px(0.);\n                            let mut first_visible_element_ix = 0;\n                            for (i, &size) in item_sizes.iter().enumerate() {\n                                cumulative_size += size;\n                                if cumulative_size > -(scroll_offset.y + paddings.top) {\n                                    first_visible_element_ix = i;\n                                    break;\n                                }\n                            }\n\n                            cumulative_size = px(0.);\n                            let mut last_visible_element_ix = 0;\n                            for (i, &size) in item_sizes.iter().enumerate() {\n                                cumulative_size += size;\n                                if cumulative_size > (-scroll_offset.y + content_bounds.size.height)\n                                {\n                                    last_visible_element_ix = i + 1;\n                                    break;\n                                }\n                            }\n                            if last_visible_element_ix == 0 {\n                                last_visible_element_ix = self.items_count;\n                            } else {\n                                last_visible_element_ix += 1;\n                            }\n                            (first_visible_element_ix, last_visible_element_ix)\n                        }\n                    };\n\n                    let visible_range = first_visible_element_ix\n                        ..cmp::min(last_visible_element_ix, self.items_count);\n\n                    let items = (self.render_items)(visible_range.clone(), window, cx);\n\n                    let content_mask = ContentMask { bounds };\n                    window.with_content_mask(Some(content_mask), |window| {\n                        for (mut item, ix) in items.into_iter().zip(visible_range.clone()) {\n                            let item_origin = match self.axis {\n                                Axis::Horizontal => {\n                                    content_bounds.origin\n                                        + point(item_origins[ix] + scroll_offset.x, scroll_offset.y)\n                                }\n                                Axis::Vertical => {\n                                    content_bounds.origin\n                                        + point(scroll_offset.x, item_origins[ix] + scroll_offset.y)\n                                }\n                            };\n\n                            let available_space = match self.axis {\n                                Axis::Horizontal => size(\n                                    AvailableSpace::Definite(item_sizes[ix]),\n                                    AvailableSpace::Definite(content_bounds.size.height),\n                                ),\n                                Axis::Vertical => size(\n                                    AvailableSpace::Definite(content_bounds.size.width),\n                                    AvailableSpace::Definite(item_sizes[ix]),\n                                ),\n                            };\n\n                            item.layout_as_root(available_space, window, cx);\n                            item.prepaint_at(item_origin, window, cx);\n                            layout.items.push(item);\n                        }\n                    });\n                }\n\n                hitbox\n            },\n        )\n    }\n\n    fn paint(\n        &mut self,\n        global_id: Option<&GlobalElementId>,\n        inspector_id: Option<&gpui::InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        layout: &mut Self::RequestLayoutState,\n        hitbox: &mut Self::PrepaintState,\n        window: &mut Window,\n        cx: &mut App,\n    ) {\n        self.base.interactivity().paint(\n            global_id,\n            inspector_id,\n            bounds,\n            hitbox.as_ref(),\n            window,\n            cx,\n            |_, window, cx| {\n                for item in &mut layout.items {\n                    item.paint(window, cx);\n                }\n            },\n        )\n    }\n}\n"
  },
  {
    "path": "crates/ui/src/window_border.rs",
    "content": "// From:\n// https://github.com/zed-industries/zed/blob/56daba28d40301ee4c05546fadb691d070b7b2b6/crates/gpui/examples/window_shadow.rs\nuse gpui::{\n    AnyElement, App, Bounds, CursorStyle, Decorations, Edges, HitboxBehavior, Hsla,\n    InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels, Point, RenderOnce,\n    ResizeEdge, Size, Styled as _, Window, canvas, div, point, prelude::FluentBuilder as _, px,\n};\n\nuse crate::ActiveTheme;\n\n#[cfg(not(target_os = \"linux\"))]\npub(crate) const SHADOW_SIZE: Pixels = px(0.0);\n#[cfg(target_os = \"linux\")]\npub(crate) const SHADOW_SIZE: Pixels = px(12.0);\nconst BORDER_SIZE: Pixels = px(1.0);\npub(crate) const BORDER_RADIUS: Pixels = px(0.0);\n\n/// Create a new window border.\npub fn window_border() -> WindowBorder {\n    WindowBorder::new()\n}\n\n/// Window border use to render a custom window border and shadow for Linux.\n#[derive(IntoElement)]\npub struct WindowBorder {\n    shadow_size: Pixels,\n    children: Vec<AnyElement>,\n}\n\nimpl Default for WindowBorder {\n    fn default() -> Self {\n        Self {\n            shadow_size: SHADOW_SIZE,\n            children: Vec::new(),\n        }\n    }\n}\n\nimpl WindowBorder {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the shadow size for typical Linux client-side decorations.\n    ///\n    /// Default: [`SHADOW_SIZE`]\n    pub fn shadow_size(mut self, size: impl Into<Pixels>) -> Self {\n        self.shadow_size = size.into();\n        self\n    }\n}\n\n/// Get the window paddings.\npub fn window_paddings(window: &Window) -> Edges<Pixels> {\n    let shadow_size = window.client_inset().unwrap_or(SHADOW_SIZE);\n    match window.window_decorations() {\n        Decorations::Server => Edges::all(px(0.0)),\n        Decorations::Client { tiling } => {\n            let mut paddings = Edges::all(shadow_size);\n            if tiling.top {\n                paddings.top = px(0.0);\n            }\n            if tiling.bottom {\n                paddings.bottom = px(0.0);\n            }\n            if tiling.left {\n                paddings.left = px(0.0);\n            }\n            if tiling.right {\n                paddings.right = px(0.0);\n            }\n            paddings\n        }\n    }\n}\n\nimpl ParentElement for WindowBorder {\n    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {\n        self.children.extend(elements);\n    }\n}\n\nimpl RenderOnce for WindowBorder {\n    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {\n        let decorations = window.window_decorations();\n        let shadow_size = match decorations {\n            Decorations::Client { tiling }\n                if tiling.top && tiling.bottom && tiling.left && tiling.right =>\n            {\n                px(0.0)\n            }\n            _ => self.shadow_size,\n        };\n        window.set_client_inset(shadow_size);\n\n        div()\n            .id(\"window-backdrop\")\n            .bg(gpui::transparent_black())\n            .map(|div| match decorations {\n                Decorations::Server => div,\n                Decorations::Client { tiling, .. } => div\n                    .bg(gpui::transparent_black())\n                    .child(\n                        canvas(\n                            |_bounds, window, _| {\n                                window.insert_hitbox(\n                                    Bounds::new(\n                                        point(px(0.0), px(0.0)),\n                                        window.window_bounds().get_bounds().size,\n                                    ),\n                                    HitboxBehavior::Normal,\n                                )\n                            },\n                            move |_bounds, hitbox, window, _| {\n                                let mouse = window.mouse_position();\n                                let size = window.window_bounds().get_bounds().size;\n                                let Decorations::Client { tiling } = window.window_decorations() else {\n                                    return;\n                                };\n                                if tiling.top && tiling.bottom && tiling.left && tiling.right {\n                                    return;\n                                }\n                                let Some(edge) = resize_edge(mouse, shadow_size, size) else {\n                                    return;\n                                };\n                                window.set_cursor_style(\n                                    match edge {\n                                        ResizeEdge::Top | ResizeEdge::Bottom => {\n                                            CursorStyle::ResizeUpDown\n                                        }\n                                        ResizeEdge::Left | ResizeEdge::Right => {\n                                            CursorStyle::ResizeLeftRight\n                                        }\n                                        ResizeEdge::TopLeft | ResizeEdge::BottomRight => {\n                                            CursorStyle::ResizeUpLeftDownRight\n                                        }\n                                        ResizeEdge::TopRight | ResizeEdge::BottomLeft => {\n                                            CursorStyle::ResizeUpRightDownLeft\n                                        }\n                                    },\n                                    &hitbox,\n                                );\n                            },\n                        )\n                        .size_full()\n                        .absolute(),\n                    )\n                    .when(!(tiling.top || tiling.right), |div| {\n                        div.rounded_tr(BORDER_RADIUS)\n                    })\n                    .when(!(tiling.top || tiling.left), |div| {\n                        div.rounded_tl(BORDER_RADIUS)\n                    })\n                    .when(!tiling.top, |div| div.pt(shadow_size))\n                    .when(!tiling.bottom, |div| div.pb(shadow_size))\n                    .when(!tiling.left, |div| div.pl(shadow_size))\n                    .when(!tiling.right, |div| div.pr(shadow_size))\n                    .on_mouse_down(MouseButton::Left, move |_, window, _| {\n                        let Decorations::Client { tiling } = window.window_decorations() else {\n                            return;\n                        };\n                        if tiling.top && tiling.bottom && tiling.left && tiling.right {\n                            return;\n                        }\n                        let size = window.window_bounds().get_bounds().size;\n                        let pos = window.mouse_position();\n\n                        match resize_edge(pos, shadow_size, size) {\n                            Some(edge) => window.start_window_resize(edge),\n                            None => {}\n                        };\n                    }),\n            })\n            .size_full()\n            .child(\n                div()\n                    .cursor(CursorStyle::default())\n                    .map(|div| match decorations {\n                        Decorations::Server => div,\n                        Decorations::Client { tiling } => div\n                            .when(!(tiling.top || tiling.right), |div| {\n                                div.rounded_tr(BORDER_RADIUS)\n                            })\n                            .when(!(tiling.top || tiling.left), |div| {\n                                div.rounded_tl(BORDER_RADIUS)\n                            })\n                            .border_color(cx.theme().window_border)\n                            .when(!tiling.top, |div| div.border_t(BORDER_SIZE))\n                            .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))\n                            .when(!tiling.left, |div| div.border_l(BORDER_SIZE))\n                            .when(!tiling.right, |div| div.border_r(BORDER_SIZE))\n                            .when(!tiling.is_tiled(), |div| {\n                                div.shadow(vec![gpui::BoxShadow {\n                                    color: Hsla {\n                                        h: 0.,\n                                        s: 0.,\n                                        l: 0.,\n                                        a: 0.3,\n                                    },\n                                    blur_radius: shadow_size / 2.,\n                                    spread_radius: px(0.),\n                                    offset: point(px(0.0), px(0.0)),\n                                }])\n                            }),\n                    })\n                    .on_mouse_move(|_e, _, cx| {\n                        cx.stop_propagation();\n                    })\n                    .bg(gpui::transparent_black())\n                    .size_full()\n                    .children(self.children),\n            )\n    }\n}\n\nfn resize_edge(pos: Point<Pixels>, shadow_size: Pixels, size: Size<Pixels>) -> Option<ResizeEdge> {\n    let edge = if pos.y < shadow_size && pos.x < shadow_size {\n        ResizeEdge::TopLeft\n    } else if pos.y < shadow_size && pos.x > size.width - shadow_size {\n        ResizeEdge::TopRight\n    } else if pos.y < shadow_size {\n        ResizeEdge::Top\n    } else if pos.y > size.height - shadow_size && pos.x < shadow_size {\n        ResizeEdge::BottomLeft\n    } else if pos.y > size.height - shadow_size && pos.x > size.width - shadow_size {\n        ResizeEdge::BottomRight\n    } else if pos.y > size.height - shadow_size {\n        ResizeEdge::Bottom\n    } else if pos.x < shadow_size {\n        ResizeEdge::Left\n    } else if pos.x > size.width - shadow_size {\n        ResizeEdge::Right\n    } else {\n        return None;\n    };\n    Some(edge)\n}\n"
  },
  {
    "path": "crates/ui/src/window_ext.rs",
    "content": "use crate::{\n    Placement, Root,\n    dialog::{AlertDialog, Dialog},\n    input::InputState,\n    notification::Notification,\n    sheet::Sheet,\n};\nuse gpui::{App, Entity, Window};\nuse std::rc::Rc;\n\n/// Extension trait for [`Window`] to add dialog, sheet .. functionality.\npub trait WindowExt: Sized {\n    /// Opens a Sheet at right placement.\n    fn open_sheet<F>(&mut self, cx: &mut App, build: F)\n    where\n        F: Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static;\n\n    /// Opens a Sheet at the given placement.\n    fn open_sheet_at<F>(&mut self, placement: Placement, cx: &mut App, build: F)\n    where\n        F: Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static;\n\n    /// Return true, if there is an active Sheet.\n    fn has_active_sheet(&mut self, cx: &mut App) -> bool;\n\n    /// Closes the active Sheet.\n    fn close_sheet(&mut self, cx: &mut App);\n\n    /// Opens a Dialog.\n    fn open_dialog<F>(&mut self, cx: &mut App, build: F)\n    where\n        F: Fn(Dialog, &mut Window, &mut App) -> Dialog + 'static;\n\n    /// Opens an AlertDialog.\n    ///\n    /// This is a convenience method for opening an alert dialog with opinionated defaults.\n    /// The footer buttons are center-aligned and include an icon based on the variant.\n    ///\n    /// # Examples\n    ///\n    /// ```ignore\n    /// use gpui_component::{AlertDialog, alert::AlertVariant};\n    ///\n    /// window.open_alert_dialog(cx, |alert, _, _| {\n    ///     alert.warning()\n    ///         .title(\"Unsaved Changes\")\n    ///         .description(\"You have unsaved changes. Are you sure you want to leave?\")\n    ///         .show_cancel(true)\n    /// });\n    /// ```\n    fn open_alert_dialog<F>(&mut self, cx: &mut App, build: F)\n    where\n        F: Fn(AlertDialog, &mut Window, &mut App) -> AlertDialog + 'static;\n\n    /// Return true, if there is an active Dialog.\n    fn has_active_dialog(&mut self, cx: &mut App) -> bool;\n\n    /// Closes the last active Dialog.\n    fn close_dialog(&mut self, cx: &mut App);\n\n    /// Closes all active Dialogs.\n    fn close_all_dialogs(&mut self, cx: &mut App);\n\n    /// Pushes a notification to the notification list.\n    fn push_notification(&mut self, note: impl Into<Notification>, cx: &mut App);\n\n    /// Removes the notification with the given id.\n    fn remove_notification<T: Sized + 'static>(&mut self, cx: &mut App);\n\n    /// Clears all notifications.\n    fn clear_notifications(&mut self, cx: &mut App);\n\n    /// Returns number of notifications.\n    fn notifications(&mut self, cx: &mut App) -> Rc<Vec<Entity<Notification>>>;\n\n    /// Return current focused Input entity.\n    fn focused_input(&mut self, cx: &mut App) -> Option<Entity<InputState>>;\n    /// Returns true if there is a focused Input entity.\n    fn has_focused_input(&mut self, cx: &mut App) -> bool;\n}\n\nimpl WindowExt for Window {\n    #[inline]\n    fn open_sheet<F>(&mut self, cx: &mut App, build: F)\n    where\n        F: Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static,\n    {\n        self.open_sheet_at(Placement::Right, cx, build)\n    }\n\n    #[inline]\n    fn open_sheet_at<F>(&mut self, placement: Placement, cx: &mut App, build: F)\n    where\n        F: Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static,\n    {\n        Root::update(self, cx, move |root, window, cx| {\n            root.open_sheet_at(placement, build, window, cx);\n        })\n    }\n\n    #[inline]\n    fn has_active_sheet(&mut self, cx: &mut App) -> bool {\n        Root::read(self, cx).active_sheet.is_some()\n    }\n\n    #[inline]\n    fn close_sheet(&mut self, cx: &mut App) {\n        Root::update(self, cx, |root, window, cx| {\n            root.close_sheet(window, cx);\n        })\n    }\n\n    #[inline]\n    fn open_dialog<F>(&mut self, cx: &mut App, build: F)\n    where\n        F: Fn(Dialog, &mut Window, &mut App) -> Dialog + 'static,\n    {\n        Root::update(self, cx, move |root, window, cx| {\n            root.open_dialog(build, window, cx);\n        })\n    }\n\n    #[inline]\n    fn open_alert_dialog<F>(&mut self, cx: &mut App, build: F)\n    where\n        F: Fn(AlertDialog, &mut Window, &mut App) -> AlertDialog + 'static,\n    {\n        self.open_dialog(cx, move |_, window, cx| {\n            build(AlertDialog::new(cx), window, cx).into_dialog(window, cx)\n        })\n    }\n\n    #[inline]\n    fn has_active_dialog(&mut self, cx: &mut App) -> bool {\n        Root::read(self, cx).active_dialogs.len() > 0\n    }\n\n    #[inline]\n    fn close_dialog(&mut self, cx: &mut App) {\n        Root::update(self, cx, |root, window, cx| {\n            root.close_dialog(window, cx);\n        })\n    }\n\n    #[inline]\n    fn close_all_dialogs(&mut self, cx: &mut App) {\n        Root::update(self, cx, |root, window, cx| {\n            root.close_all_dialogs(window, cx);\n        })\n    }\n\n    #[inline]\n    fn push_notification(&mut self, note: impl Into<Notification>, cx: &mut App) {\n        let note = note.into();\n        Root::update(self, cx, |root, window, cx| {\n            root.push_notification(note, window, cx);\n        })\n    }\n\n    #[inline]\n    fn remove_notification<T: Sized + 'static>(&mut self, cx: &mut App) {\n        Root::update(self, cx, |root, window, cx| {\n            root.remove_notification::<T>(window, cx);\n        })\n    }\n\n    #[inline]\n    fn clear_notifications(&mut self, cx: &mut App) {\n        Root::update(self, cx, |root, window, cx| {\n            root.clear_notifications(window, cx);\n        })\n    }\n\n    #[inline]\n    fn notifications(&mut self, cx: &mut App) -> Rc<Vec<Entity<Notification>>> {\n        Rc::new(Root::read(self, cx).notification.read(cx).notifications())\n    }\n\n    #[inline]\n    fn has_focused_input(&mut self, cx: &mut App) -> bool {\n        Root::read(self, cx).focused_input.is_some()\n    }\n\n    #[inline]\n    fn focused_input(&mut self, cx: &mut App) -> Option<Entity<InputState>> {\n        Root::read(self, cx).focused_input.clone()\n    }\n}\n"
  },
  {
    "path": "crates/webview/Cargo.toml",
    "content": "[package]\nname = \"gpui-wry\"\ndescription = \"WebView support for GPUI, based on Wry.\"\nkeywords = [\"webview\", \"gpui\",\"wry\"]\nlicense = \"Apache-2.0\"\ndocumentation = \"https://docs.rs/gpui-component\"\nhomepage = \"https://longbridge.github.io/gpui-component\"\nrepository = \"https://github.com/longbridge/gpui-component/tree/main/crates/webview\"\nreadme = \"README.md\"\nedition.workspace = true\npublish = true\nversion = \"0.5.0\"\n\n[features]\ninspector = [\"wry/devtools\"]\n\n[lib]\ndoctest = false\n\n[dependencies]\nanyhow.workspace = true\ngpui.workspace = true\nwry = { version = \"0.53.3\", package = \"lb-wry\" }\n"
  },
  {
    "path": "crates/webview/README.md",
    "content": "# Wry for GPUI\n\nA webview supports for GPUI, based on [Wry](https://github.com/tauri-apps/wry).\n\nThis still a experimental with limited features, please file issues for any bugs or missing features.\n\n- The WebView will render on top of the GPUI window, any GPUI elements behind the WebView bounds will be covered.\n- Only supports macOS and Windows currently.\n\nSo, we recommend using the webview in a separate window or in a Popup layer.\n\n## Run Example\n\nIn the root of the repository, run:\n\n```\ncargo run -p webview\n```\n\n## License\n\nApache-2.0\n"
  },
  {
    "path": "crates/webview/src/lib.rs",
    "content": "use std::{ops::Deref, rc::Rc};\n\nuse wry::{\n    Rect,\n    dpi::{self, LogicalSize},\n};\n\nuse gpui::{\n    App, Bounds, ContentMask, DismissEvent, Element, ElementId, Entity, EventEmitter, FocusHandle,\n    Focusable, GlobalElementId, Hitbox, InteractiveElement, IntoElement, LayoutId, MouseDownEvent,\n    ParentElement as _, Pixels, Render, Size, Style, Styled as _, Window, canvas, div,\n};\n\n/// A webview based on wry WebView.\n///\n/// [experimental]\npub struct WebView {\n    focus_handle: FocusHandle,\n    webview: Rc<wry::WebView>,\n    visible: bool,\n    bounds: Bounds<Pixels>,\n}\n\nimpl Drop for WebView {\n    fn drop(&mut self) {\n        self.hide();\n    }\n}\n\nimpl WebView {\n    /// Create a new WebView from a wry WebView.\n    pub fn new(webview: wry::WebView, _: &mut Window, cx: &mut App) -> Self {\n        let _ = webview.set_bounds(Rect::default());\n\n        Self {\n            focus_handle: cx.focus_handle(),\n            visible: true,\n            bounds: Bounds::default(),\n            webview: Rc::new(webview),\n        }\n    }\n\n    /// Show the webview.\n    pub fn show(&mut self) {\n        let _ = self.webview.set_visible(true);\n        self.visible = true;\n    }\n\n    /// Hide the webview.\n    pub fn hide(&mut self) {\n        _ = self.webview.focus_parent();\n        _ = self.webview.set_visible(false);\n        self.visible = false;\n    }\n\n    /// Get whether the webview is visible.\n    pub fn visible(&self) -> bool {\n        self.visible\n    }\n\n    /// Get the current bounds of the webview.\n    pub fn bounds(&self) -> Bounds<Pixels> {\n        self.bounds\n    }\n\n    /// Go back in the webview history.\n    pub fn back(&mut self) -> anyhow::Result<()> {\n        Ok(self.webview.evaluate_script(\"history.back();\")?)\n    }\n\n    /// Load a URL in the webview.\n    pub fn load_url(&mut self, url: &str) {\n        let _ = self.webview.load_url(url);\n    }\n\n    /// Get the raw wry webview.\n    pub fn raw(&self) -> &wry::WebView {\n        &self.webview\n    }\n}\n\nimpl Deref for WebView {\n    type Target = wry::WebView;\n\n    fn deref(&self) -> &Self::Target {\n        &self.webview\n    }\n}\n\nimpl Focusable for WebView {\n    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl EventEmitter<DismissEvent> for WebView {}\n\nimpl Render for WebView {\n    fn render(\n        &mut self,\n        window: &mut gpui::Window,\n        cx: &mut gpui::Context<Self>,\n    ) -> impl IntoElement {\n        let view = cx.entity().clone();\n\n        div()\n            .track_focus(&self.focus_handle)\n            .size_full()\n            .child({\n                let view = cx.entity().clone();\n                canvas(\n                    move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),\n                    |_, _, _, _| {},\n                )\n                .absolute()\n                .size_full()\n            })\n            .child(WebViewElement::new(self.webview.clone(), view, window, cx))\n    }\n}\n\n/// A webview element can display a wry webview.\npub struct WebViewElement {\n    parent: Entity<WebView>,\n    view: Rc<wry::WebView>,\n}\n\nimpl WebViewElement {\n    /// Create a new webview element from a wry WebView.\n    pub fn new(\n        view: Rc<wry::WebView>,\n        parent: Entity<WebView>,\n        _window: &mut Window,\n        _cx: &mut App,\n    ) -> Self {\n        Self { view, parent }\n    }\n}\n\nimpl IntoElement for WebViewElement {\n    type Element = WebViewElement;\n\n    fn into_element(self) -> Self::Element {\n        self\n    }\n}\n\nimpl Element for WebViewElement {\n    type RequestLayoutState = ();\n    type PrepaintState = Option<Hitbox>;\n\n    fn id(&self) -> Option<ElementId> {\n        None\n    }\n\n    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {\n        None\n    }\n\n    fn request_layout(\n        &mut self,\n        _: Option<&GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> (LayoutId, Self::RequestLayoutState) {\n        let style = Style {\n            size: Size::full(),\n            flex_shrink: 1.,\n            ..Default::default()\n        };\n\n        // If the parent view is no longer visible, we don't need to layout the webview\n        let id = window.request_layout(style, [], cx);\n        (id, ())\n    }\n\n    fn prepaint(\n        &mut self,\n        _: Option<&GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        _: &mut Self::RequestLayoutState,\n        window: &mut Window,\n        cx: &mut App,\n    ) -> Self::PrepaintState {\n        if !self.parent.read(cx).visible() {\n            return None;\n        }\n\n        let _ = self.view.set_bounds(Rect {\n            size: dpi::Size::Logical(LogicalSize {\n                width: bounds.size.width.into(),\n                height: bounds.size.height.into(),\n            }),\n            position: dpi::Position::Logical(dpi::LogicalPosition::new(\n                bounds.origin.x.into(),\n                bounds.origin.y.into(),\n            )),\n        });\n\n        // Create a hitbox to handle mouse event\n        Some(window.insert_hitbox(bounds, gpui::HitboxBehavior::Normal))\n    }\n\n    fn paint(\n        &mut self,\n        _: Option<&GlobalElementId>,\n        _: Option<&gpui::InspectorElementId>,\n        bounds: Bounds<Pixels>,\n        _: &mut Self::RequestLayoutState,\n        hitbox: &mut Self::PrepaintState,\n        window: &mut Window,\n        _: &mut App,\n    ) {\n        let bounds = hitbox.clone().map(|h| h.bounds).unwrap_or(bounds);\n        window.with_content_mask(Some(ContentMask { bounds }), |window| {\n            let webview = self.view.clone();\n            window.on_mouse_event(move |event: &MouseDownEvent, _, _, _| {\n                if !bounds.contains(&event.position) {\n                    // Click white space to blur the input focus\n                    let _ = webview.focus_parent();\n                }\n            });\n        });\n    }\n}\n"
  },
  {
    "path": "docs/.gitignore",
    "content": ".vitepress/cache\n.vitepress/dist\nbun.lockb\n"
  },
  {
    "path": "docs/.vitepress/config.mts",
    "content": "import { defineConfig } from \"vitepress\";\nimport type { UserConfig } from \"vitepress\";\nimport { generateSidebar } from \"vitepress-sidebar\";\nimport llmstxt from \"vitepress-plugin-llms\";\nimport tailwindcss from \"@tailwindcss/vite\";\nimport { lightTheme, darkTheme } from \"./language\";\nimport { ViteToml } from \"vite-plugin-toml\";\n\n/**\n * https://github.com/jooy2/vitepress-sidebar\n */\nconst sidebar = generateSidebar([\n  {\n    scanStartPath: \"/docs/\",\n    rootGroupText: \"Introduction\",\n    collapsed: false,\n    useTitleFromFrontmatter: true,\n    useTitleFromFileHeading: true,\n    sortMenusByFrontmatterOrder: true,\n    includeRootIndexFile: false,\n  },\n]);\n\n// https://vitepress.dev/reference/site-config\nconst config: UserConfig = {\n  title: \"GPUI Component\",\n  base: \"/gpui-component/\",\n  description:\n    \"Rust GUI components for building fantastic cross-platform desktop application by using GPUI.\",\n  cleanUrls: true,\n  head: [\n    [\n      \"link\",\n      {\n        rel: \"icon\",\n        href: \"/gpui-component/logo.svg\",\n        media: \"(prefers-color-scheme: light)\",\n      },\n    ],\n    [\n      \"link\",\n      {\n        rel: \"icon\",\n        href: \"/gpui-component/logo-dark.svg\",\n        media: \"(prefers-color-scheme: dark)\",\n      },\n    ],\n  ],\n  vite: {\n    plugins: [llmstxt(), tailwindcss(), ViteToml()],\n  },\n  themeConfig: {\n    logo: {\n      light: \"/logo.svg\",\n      dark: \"/logo-dark.svg\",\n    },\n    footer: {\n      message: `GPUI Component is an open source project under the Apache-2.0 License,\n        developed by <a href='https://longbridge.com' target='_blank'>Longbridge</a>.`,\n      copyright: `\n        <a href=\"https://gpui.rs\">GPUI</a>\n        |\n        <a href=\"/gpui-component/gallery/\" target=\"_blank\">Gallery</a>\n        |\n        <a href=\"/gpui-component/contributors\">Contributors</a>\n        |\n        <a href=\"/gpui-component/skills\" target=\"_blank\">Skills</a>\n        |\n        <a href=\"/gpui-component/llms-full.txt\" target=\"_blank\">llms-full.txt</a>\n        |\n        <a href=\"https://github.com/longbridge/gpui-component/issues\" target=\"_blank\">Report Bug</a>\n        |\n        <a href=\"https://github.com/longbridge/gpui-component/discussions\" target=\"_blank\">Discussion</a>\n        <br />\n        Icon resources are used <a href=\"https://lucide.dev\" target=\"_blank\">Lucide</a>,\n        <a href=\"https://isocons.app\" target=\"_blank\">Isocons</a>.\n      `,\n    },\n    // https://vitepress.dev/reference/default-theme-config\n    nav: [\n      { text: \"Home\", link: \"/\" },\n      { text: \"Getting Started\", link: \"/docs/getting-started\" },\n      { text: \"Components\", link: \"/docs/components\" },\n      { text: \"Gallery\", link: \"/gallery/\", target: \"_blank\" },\n      { text: \"API Doc\", link: \"https://docs.rs/gpui-component\" },\n      {\n        text: \"Resources\",\n        items: [\n          {\n            text: \"Contributors\",\n            link: \"/contributors\",\n          },\n          {\n            text: \"Releases\",\n            link: \"https://github.com/longbridge/gpui-component/releases\",\n          },\n          {\n            text: \"Issues\",\n            link: \"https://github.com/longbridge/gpui-component/issues\",\n          },\n          {\n            text: \"Discussion\",\n            link: \"https://github.com/longbridge/gpui-component/discussions\",\n          },\n        ],\n      },\n      {\n        component: \"GitHubStar\",\n      },\n    ],\n\n    sidebar: sidebar as any,\n\n    socialLinks: null,\n    editLink: {\n      pattern:\n        \"https://github.com/longbridge/gpui-component/edit/main/docs/:path\",\n    },\n    search: {\n      provider: \"local\",\n    },\n  },\n  markdown: {\n    math: true,\n    defaultHighlightLang: \"rs\",\n    theme: {\n      light: lightTheme,\n      dark: darkTheme,\n    },\n  },\n};\n\nexport default defineConfig(config);\n"
  },
  {
    "path": "docs/.vitepress/language.ts",
    "content": "import { readFileSync } from \"fs\";\n\nconst lightTheme = JSON.parse(readFileSync(\"src/light.theme.json\").toString());\nconst darkTheme = JSON.parse(readFileSync(\"src/dark.theme.json\").toString());\n\nexport { darkTheme, lightTheme };\n"
  },
  {
    "path": "docs/.vitepress/theme/components/GitHubStar.vue",
    "content": "<template>\n    <a\n        href=\"https://github.com/longbridge/gpui-component\"\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n        class=\"github-star-button\"\n        :title=\"`${stargazers_count} stars on GitHub`\"\n    >\n        <svg viewBox=\"0 0 438.549 438.549\">\n            <path\n                fill=\"currentColor\"\n                d=\"M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z\"\n            ></path>\n        </svg>\n        <span>{{ starLabel }}</span>\n    </a>\n</template>\n\n<script setup>\nimport { data } from \"../../../data/repo.data\";\nlet stargazers_count = data.stargazers_count;\n\nconst starLabel =\n    stargazers_count / 1000 >= 1\n        ? (stargazers_count / 1000.0).toFixed(1) + \"k\"\n        : stargazers_count.toString();\n</script>\n\n<style lang=\"scss\" scoped>\n@reference \"../style.css\";\n\n.github-star-button {\n    @apply flex w-full py-3 px-0 lg:inline-flex lg:w-auto lg:py-0 lg:px-2;\n    @apply items-center text-sm gap-2 text-[var(--vp-c-text-1)] rounded hover:text-[var(--vp-c-brand-1)] no-underline;\n\n    svg {\n        @apply w-5 h-5;\n    }\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/index.ts",
    "content": "// https://vitepress.dev/guide/custom-theme\nimport { h } from \"vue\";\nimport type { Theme } from \"vitepress\";\nimport DefaultTheme from \"vitepress/theme\";\nimport \"./style.css\";\nimport GitHubStar from \"./components/GitHubStar.vue\";\nimport config from \"../../../crates/ui/Cargo.toml\";\n\n/** @type {import('vitepress').Theme} */\nexport default {\n  extends: DefaultTheme,\n  Layout: () => {\n    return h(DefaultTheme.Layout, null, {\n      // https://vitepress.dev/guide/extending-default-theme#layout-slots\n    });\n  },\n  enhanceApp({ app, router, siteData }) {\n    // ...\n    app.component(\"GitHubStar\", GitHubStar);\n\n    app.config.globalProperties.GPUI_VERSION = \"0.2.2\";\n    app.config.globalProperties.VERSION = config.package.version;\n  },\n} satisfies Theme;\n"
  },
  {
    "path": "docs/.vitepress/theme/style.css",
    "content": "/**\n * Customize default theme styling by overriding CSS variables:\n * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css\n */\n\n@import \"tailwindcss\";\n@custom-variant dark (&:where(.dark, .dark *));\n\n:root {\n  --radius: 0.875rem;\n  --background: oklch(1 0 0);\n  --foreground: oklch(0.145 0 0);\n  --card: oklch(1 0 0);\n  --card-foreground: oklch(0.145 0 0);\n  --popover: oklch(1 0 0);\n  --popover-foreground: oklch(0.145 0 0);\n  --primary: oklch(0.205 0 0);\n  --primary-foreground: oklch(0.985 0 0);\n  --secondary: oklch(0.97 0 0);\n  --secondary-foreground: oklch(0.205 0 0);\n  --muted: oklch(0.97 0 0);\n  --muted-foreground: oklch(0.556 0 0);\n  --accent: oklch(0.97 0 0);\n  --accent-foreground: oklch(0.205 0 0);\n  --destructive: oklch(0.577 0.245 27.325);\n  --border: oklch(0.922 0 0);\n  --input: oklch(0.922 0 0);\n  --ring: oklch(0.708 0 0);\n  --chart-1: oklch(0.646 0.222 41.116);\n  --chart-2: oklch(0.6 0.118 184.704);\n  --chart-3: oklch(0.398 0.07 227.392);\n  --chart-4: oklch(0.828 0.189 84.429);\n  --chart-5: oklch(0.769 0.188 70.08);\n  --sidebar: oklch(0.985 0 0);\n  --sidebar-foreground: oklch(0.145 0 0);\n  --sidebar-primary: oklch(0.205 0 0);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.97 0 0);\n  --sidebar-accent-foreground: oklch(0.205 0 0);\n  --sidebar-border: oklch(0.922 0 0);\n  --sidebar-ring: oklch(0.708 0 0);\n\n  --vp-c-bg: var(--background);\n  --vp-c-bg-alt: var(--secondary);\n  --vp-c-bg-elv: var(--popover);\n  --vp-c-bg-soft: var(--secondary);\n\n  --vp-c-text-1: var(--foreground);\n  --vp-c-brand-1: var(--primary);\n  --vp-c-default-1: var(--secondary);\n  --vp-c-default-2: var(--muted);\n\n  --vp-nav-bg-color: var(--background);\n  --vp-custom-block-tip-border: var(--vp-c-default-3);\n  --vp-custom-block-tip-text: var(--vp-c-text-1);\n  --vp-custom-block-tip-bg: transparent;\n  --vp-custom-block-tip-code-bg: var(--vp-c-default-soft);\n  --vp-input-switch-bg-color: var(--secondary);\n  --vp-input-border-color: var(--input);\n  --vp-c-divider: var(--border);\n}\n\n.dark {\n  --background: oklch(0.145 0 0);\n  --foreground: oklch(0.985 0 0);\n  --card: oklch(0.205 0 0);\n  --card-foreground: oklch(0.985 0 0);\n  --popover: oklch(0.269 0 0);\n  --popover-foreground: oklch(0.985 0 0);\n  --primary: oklch(0.922 0 0);\n  --primary-foreground: oklch(0.205 0 0);\n  --secondary: oklch(0.269 0 0);\n  --secondary-foreground: oklch(0.985 0 0);\n  --muted: oklch(0.269 0 0);\n  --muted-foreground: oklch(0.708 0 0);\n  --accent: oklch(0.371 0 0);\n  --accent-foreground: oklch(0.985 0 0);\n  --destructive: oklch(0.704 0.191 22.216);\n  --border: oklch(1 0 0 / 10%);\n  --input: oklch(1 0 0 / 15%);\n  --ring: oklch(0.556 0 0);\n  --chart-1: oklch(0.488 0.243 264.376);\n  --chart-2: oklch(0.696 0.17 162.48);\n  --chart-3: oklch(0.769 0.188 70.08);\n  --chart-4: oklch(0.627 0.265 303.9);\n  --chart-5: oklch(0.645 0.246 16.439);\n  --sidebar: oklch(0.205 0 0);\n  --sidebar-foreground: oklch(0.985 0 0);\n  --sidebar-primary: oklch(0.488 0.243 264.376);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.269 0 0);\n  --sidebar-accent-foreground: oklch(0.985 0 0);\n  --sidebar-border: oklch(1 0 0 / 10%);\n  --sidebar-ring: oklch(0.439 0 0);\n}\n\n#app {\n  .VPMenu {\n    @apply p-1 rounded-md bg-(--popover);\n\n    .link {\n      @apply text-sm py-1;\n    }\n  }\n\n  .DocSearch-Button {\n    @apply rounded-full py-0 px-2 h-8 lg:w-56 xl:w-64;\n\n    .DocSearch-Button-Keys {\n      @apply rounded-full p-1;\n    }\n  }\n\n  .VPNav {\n    @apply bg-(--background) border-b border-(--border);\n\n    .divider {\n      @apply hidden;\n    }\n  }\n\n  .VPSwitch {\n    @apply border-(--secondary) bg-(--secondary) hover:border-(--border);\n  }\n\n  .VPFooter {\n    @apply border-dashed;\n  }\n\n  .VPSidebar {\n    @apply border-r border-(--border) bg-(--background);\n  }\n\n  .VPNavBarTitle {\n    a.title {\n      border-bottom: 0 !important;\n    }\n  }\n\n  .prev-next {\n    @apply border-0 pt-0;\n\n    .pager-link {\n      @apply border-dashed;\n    }\n  }\n}\n\n.VPContent.has-sidebar {\n  .VPDoc {\n    @apply pt-32;\n    background: url(\"/components.svg\") no-repeat;\n    background-position: top -45px right 320px;\n  }\n}\n\n.vp-doc {\n  h2 {\n    @apply border-t-0 mt-10 pt-0 pb-3 border-b border-dashed border-(--border);\n\n    .header-anchor {\n      @apply top-0;\n    }\n  }\n  [class*=\"language-\"] pre {\n    @apply rounded-md py-5 text-base font-mono;\n  }\n\n  [class*=\"language-\"] pre code {\n    @apply px-5;\n  }\n\n  [class*=\"language-\"] > button.copy {\n    /*rtl:ignore*/\n    direction: ltr;\n    position: absolute;\n    top: 12px;\n    /*rtl:ignore*/\n    z-index: 3;\n    border: 1px solid var(--vp-code-copy-code-border-color);\n    border-radius: 4px;\n    background-color: var(--vp-code-copy-code-bg);\n    opacity: 0;\n    cursor: pointer;\n    background-image: var(--vp-icon-copy);\n    background-position: 50%;\n    background-repeat: no-repeat;\n    transition:\n      border-color 0.25s,\n      background-color 0.25s,\n      opacity 0.25s;\n\n    width: 24px;\n    height: 24px;\n    background-size: 14px;\n\n    &:hover,\n    &.copy.copied {\n      border-color: var(--vp-code-copy-code-hover-border-color);\n      background-color: var(--vp-code-copy-code-hover-bg);\n    }\n\n    &:hover.copied::before,\n    &.copied::before {\n      height: 24px;\n      line-height: 24px;\n      padding: 0 8px;\n    }\n  }\n\n  [class*=\"language-\"] > button.copy {\n    right: 12px;\n  }\n}\n"
  },
  {
    "path": "docs/README.md",
    "content": "# gpui-component-docs\n\nTo install dependencies:\n\n```bash\nbun install\n```\n\nTo run:\n\n```bash\nbun run dev\n```\n\nThis project was created using `bun init` in bun v1.2.23. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.\n"
  },
  {
    "path": "docs/contributors.md",
    "content": "---\nlayout: home\n---\n\n<script setup>\nimport Contributors from './contributors.vue'\n</script>\n\n<Contributors />\n"
  },
  {
    "path": "docs/contributors.vue",
    "content": "<template>\n    <div class=\"contributors-page\">\n        <h1>Contributors</h1>\n        <p>Thanks to all the people who have contributed to this project!</p>\n        <div class=\"contributors-list\">\n            <a\n                :href=\"contributor.html_url\"\n                v-for=\"contributor in contributors\"\n                :key=\"contributor.id\"\n                class=\"contributor-card\"\n                rel=\"noopener noreferrer\"\n            >\n                <img\n                    :src=\"contributor.avatar_url\"\n                    :alt=\"contributor.login\"\n                    class=\"contributor-avatar\"\n                />\n                <div class=\"contributor-info\">\n                    {{ contributor.login }}\n                </div>\n            </a>\n        </div>\n        <div class=\"mt-6 text-(--muted-foreground)\">\n            More contributors not shown here. See the full\n            <a\n                href=\"https://github.com/longbridge/gpui-component/graphs/contributors\"\n                target=\"_blank\"\n            >\n                Contributors</a\n            >\n            on GitHub.\n        </div>\n    </div>\n</template>\n\n<script setup>\nimport { data } from \"./data/contributors.data\";\nconst contributors = data;\n</script>\n\n<style lang=\"scss\" scoped>\n@reference \"./.vitepress/theme/style.css\";\n\n.contributors-page {\n    @apply py-10 pt-30;\n    background: url(\"/contributors.svg\") no-repeat;\n    background-position: top 20px right 20px;\n}\n\n.contributors-list {\n    @apply mt-8 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 border-r border-b border-(--border);\n}\n\n.contributor-card {\n    @apply px-4 py-3 gap-3 border border-(--border) hover:bg-(--secondary) transition-shadow\n    items-center text-center text-(--foreground) hover:text-(--foreground) justify-center no-underline flex flex-col\n    border border-b-0 border-(--border);\n\n    @apply border-r-0 last:border-r-0 md:last:border-b-0 lg:last:border-b-0;\n\n    .contributor-avatar {\n        @apply w-12 h-12 rounded-full;\n    }\n}\n</style>\n"
  },
  {
    "path": "docs/data/contributors.data.js",
    "content": "const IGNORE_LOGINS = [\"dependabot[bot]\", \"copilot\"];\nconst API_URL =\n  \"https://api.github.com/repos/longbridge/gpui-component/contributors\";\n\nexport default {\n  async load() {\n    return await fetch(API_URL)\n      .then((res) => res.json())\n      .then((items) => {\n        let filtered = items.filter(\n          (item) => !IGNORE_LOGINS.includes(item.login.toLowerCase()),\n        );\n        return filtered.slice(0, 24);\n      });\n  },\n};\n"
  },
  {
    "path": "docs/data/repo.data.js",
    "content": "const API_URL = \"https://api.github.com/repos/longbridge/gpui-component\";\n\nexport default {\n  async load() {\n    return await fetch(API_URL).then((res) => res.json());\n  },\n};\n"
  },
  {
    "path": "docs/data/skills.data.js",
    "content": "import { readdirSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { dirname } from \"path\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nfunction parseFrontmatter(content) {\n  const frontmatterRegex = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n([\\s\\S]*)$/;\n  const match = content.match(frontmatterRegex);\n  \n  if (!match) {\n    return { frontmatter: {}, content: content.trim() };\n  }\n  \n  const frontmatterText = match[1];\n  const body = match[2];\n  \n  const frontmatter = {};\n  frontmatterText.split('\\n').forEach(line => {\n    const colonIndex = line.indexOf(':');\n    if (colonIndex > 0) {\n      const key = line.substring(0, colonIndex).trim();\n      let value = line.substring(colonIndex + 1).trim();\n      // Remove quotes if present\n      if ((value.startsWith('\"') && value.endsWith('\"')) || \n          (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n        value = value.slice(1, -1);\n      }\n      frontmatter[key] = value;\n    }\n  });\n  \n  return { frontmatter, content: body.trim() };\n}\n\nexport default {\n  async load() {\n    const skillsDir = join(__dirname, \"../../.claude/skills\");\n    const skills = [];\n    \n    try {\n      const skillDirs = readdirSync(skillsDir, { withFileTypes: true })\n        .filter(dirent => dirent.isDirectory())\n        .map(dirent => dirent.name);\n      \n      for (const skillDir of skillDirs) {\n        const skillPath = join(skillsDir, skillDir, \"SKILL.md\");\n        try {\n          const content = readFileSync(skillPath, \"utf-8\");\n          const { frontmatter, content: body } = parseFrontmatter(content);\n          \n          skills.push({\n            id: skillDir,\n            name: frontmatter.name || skillDir,\n            description: frontmatter.description || \"\",\n            content: body,\n            path: skillPath,\n          });\n        } catch (err) {\n          console.warn(`Failed to read skill ${skillDir}:`, err.message);\n        }\n      }\n      \n      // Sort skills by name\n      skills.sort((a, b) => a.name.localeCompare(b.name));\n      \n      return skills;\n    } catch (err) {\n      console.error(\"Failed to load skills:\", err);\n      return [];\n    }\n  },\n};\n"
  },
  {
    "path": "docs/docs/assets.md",
    "content": "---\ntitle: Icons & Assets\norder: -4\n---\n\n# Icons & Assets\n\nThe [IconName] and [Icon] in GPUI Component provide a comprehensive set of icons and assets that can be easily integrated into your GPUI applications.\n\nBut for minimal size applications, **we have not embedded any icon assets by default** in `gpui-component` crate.\n\nWe split the icon assets into a separate crate [gpui-component-assets] to allow developers to choose whether to include the icon assets in their applications or if you don't need the icons at all, you can build your own assets.\n\n## Use default bundled assets\n\nThe [gpui-component-assets] crate provides a default bundled assets implementation that includes all the icon files in the `assets/icons` folder.\n\nTo use the default bundled assets, you need to add the `gpui-component-assets` crate as a dependency in your `Cargo.toml`:\n\n```toml-vue\n[dependencies]\ngpui-component = \"{{ VERSION }}\"\ngpui-component-assets = \"{{ VERSION }}\"\n```\n\nThen we need call the `with_assets` method when creating the GPUI application to register the asset source:\n\n```rs\nuse gpui::*;\nuse gpui_component_assets::Assets;\n\nlet app = gpui_platform::application().with_assets(Assets);\n```\n\nNow, we can use `IconName` and `Icon` in our application as usual, the all icon assets are loaded from the default bundled assets.\n\nContinue [Use the icons](#use-the-icons) section to see how to use the icons in your application.\n\n## Build you own assets\n\nYou may have a specific set of icons that you want to use in your application, or you may want to reduce the size of your application binary by including only the icons you need.\n\nIn this case, you can build your own assets by following these steps.\n\nThe [assets](https://github.com/longbridge/gpui-component/tree/main/crates/assets/assets/) folder in source code contains all the available icons in SVG format, every file is that GPUI Component support, it matched with the [IconName] enum.\n\nYou can download the SVG files you need from the [assets] folder, or you can use your own SVG files by following the [IconName] naming convention.\n\nIn GPUI application, we can use the [rust-embed] crate to embed the SVG files into the application binary.\n\nAnd GPUI Application providers an `AssetSource` trait to load the assets.\n\n```rs\nuse anyhow::anyhow;\nuse gpui::*;\nuse gpui_component::{v_flex, IconName, Root};\nuse rust_embed::RustEmbed;\nuse std::borrow::Cow;\n\n/// An asset source that loads assets from the `./assets` folder.\n#[derive(RustEmbed)]\n#[folder = \"./assets\"]\n#[include = \"icons/**/*.svg\"]\npub struct Assets;\n\nimpl AssetSource for Assets {\n    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {\n        if path.is_empty() {\n            return Ok(None);\n        }\n\n        Self::get(path)\n            .map(|f| Some(f.data))\n            .ok_or_else(|| anyhow!(\"could not find asset at path \\\"{path}\\\"\"))\n    }\n\n    fn list(&self, path: &str) -> Result<Vec<SharedString>> {\n        Ok(Self::iter()\n            .filter_map(|p| p.starts_with(path).then(|| p.into()))\n            .collect())\n    }\n}\n```\n\nWe need call the `with_assets` method when creating the GPUI application to register the asset source:\n\n```rs\nfn main() {\n    // Register Assets to GPUI application.\n    let app = gpui_platform::application().with_assets(Assets);\n\n    app.run(move |cx| {\n        // We must initialize gpui_component before using it.\n        gpui_component::init(cx);\n\n        cx.spawn(async move |cx| {\n            cx.open_window(WindowOptions::default(), |window, cx| {\n                let view = cx.new(|_| Example);\n                // The first level on the window must be Root.\n                cx.new(|cx| Root::new(view, window, cx))\n            })\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n```\n\n## Use the icons\n\nNow we can use the icons in our application:\n\n```rs\npub struct Example;\n\nimpl Render for Example {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_2()\n            .size_full()\n            .items_center()\n            .justify_center()\n            .text_center()\n            .child(IconName::Inbox)\n            .child(IconName::Bot)\n    }\n}\n```\n\n## Resources\n\n- [Lucide Icons](https://lucide.dev/) - The icon set used in GPUI Component is based on the open-source Lucide Icons library, which provides a wide range of customizable SVG icons.\n\n[rust-embed]: https://docs.rs/rust-embed/latest/rust_embed/\n[IconName]: https://docs.rs/gpui_component/latest/gpui_component/icon/enum.IconName.html\n[Icon]: https://docs.rs/gpui_component/latest/gpui_component/icon/struct.Icon.html\n[assets]: https://github.com/longbridge/gpui-component/tree/main/crates/assets/assets/\n[gpui-component-assets]: https://crates.io/crates/gpui-component-assets\n"
  },
  {
    "path": "docs/docs/components/accordion.md",
    "content": "---\ntitle: Accordion\ndescription: The accordion uses collapse internally to make it collapsible.\n---\n\n# Accordion\n\nAn accordion component that allows users to show and hide sections of content. It uses collapse functionality internally to create collapsible panels.\n\n## Import\n\n```rust\nuse gpui_component::accordion::Accordion;\n```\n\n## Usage\n\n### Basic Accordion\n\n```rust\nAccordion::new(\"my-accordion\")\n    .item(|item| {\n        item.title(\"Section 1\")\n            .child(\"Content for section 1\")\n    })\n    .item(|item| {\n        item.title(\"Section 2\")\n            .child(\"Content for section 2\")\n    })\n    .item(|item| {\n        item.title(\"Section 3\")\n            .child(\"Content for section 3\")\n    })\n```\n\n### Multiple Open Items\n\nBy default, only one accordion item can be open at a time. Use `multiple()` to allow multiple items to be open:\n\n```rust\nAccordion::new(\"my-accordion\")\n    .multiple(true)\n    .item(|item| item.title(\"Section 1\").child(\"Content 1\"))\n    .item(|item| item.title(\"Section 2\").child(\"Content 2\"))\n```\n\n### With Borders\n\n```rust\nAccordion::new(\"my-accordion\")\n    .bordered(true)\n    .item(|item| item.title(\"Section 1\").child(\"Content 1\"))\n```\n\n### Different Sizes\n\n```rust\nuse gpui_component::{Sizable as _, Size};\n\nAccordion::new(\"my-accordion\")\n    .small()\n    .item(|item| item.title(\"Small Section\").child(\"Content\"))\n\nAccordion::new(\"my-accordion\")\n    .large()\n    .item(|item| item.title(\"Large Section\").child(\"Content\"))\n```\n\n### Handle Toggle Events\n\n```rust\nAccordion::new(\"my-accordion\")\n    .on_toggle_click(|open_indices, window, cx| {\n        println!(\"Open items: {:?}\", open_indices);\n    })\n    .item(|item| item.title(\"Section 1\").child(\"Content 1\"))\n```\n\n### Disabled State\n\n```rust\nAccordion::new(\"my-accordion\")\n    .disabled(true)\n    .item(|item| item.title(\"Disabled Section\").child(\"Content\"))\n```\n\n## API Reference\n\n- [Accordion]\n- [AccordionItem]\n\n### Sizing\n\nImplements [Sizable] trait:\n\n- `small()` - Small size\n- `medium()` - Medium size (default)\n- `large()` - Large size\n- `xsmall()` - Extra small size\n\n## Examples\n\n### With Custom Icons\n\n```rust\nAccordion::new(\"my-accordion\")\n    .item(|item| {\n        item.title(\n            h_flex()\n                .gap_2()\n                .child(Icon::new(IconName::Settings))\n                .child(\"Settings\")\n        )\n        .child(\"Settings content here\")\n    })\n```\n\n### Nested Accordions\n\n```rust\nAccordion::new(\"outer\")\n    .item(|item| {\n        item.title(\"Parent Section\")\n            .child(\n                Accordion::new(\"inner\")\n                    .item(|item| item.title(\"Child 1\").child(\"Content\"))\n                    .item(|item| item.title(\"Child 2\").child(\"Content\"))\n            )\n    })\n```\n\n[Accordion]: https://docs.rs/gpui-component/latest/gpui_component/accordion/struct.Accordion.html\n[AccordionItem]: https://docs.rs/gpui-component/latest/gpui_component/accordion/struct.AccordionItem.html\n[Sizable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Sizable.html\n"
  },
  {
    "path": "docs/docs/components/alert-dialog.md",
    "content": "---\ntitle: AlertDialog\ndescription: A modal dialog that interrupts the user with important content and expects a response.\n---\n\n# AlertDialog\n\nAlertDialog is a modal dialog component that interrupts the user with important content and expects a response. It is built on top of the [Dialog] component with opinionated defaults and a simplified API.\n\n## Differences from Dialog\n\nAlertDialog provides these defaults on top of Dialog:\n\n- Not overlay closable by default (can be changed with `overlay_closable(true)`)\n- No close button by default (can be changed with `close_button(true)`)\n- Footer buttons are center-aligned (Dialog uses right-alignment)\n- Simplified API focused on alert and confirmation scenarios\n\n## Import\n\n```rust\nuse gpui_component::dialog::{AlertDialog, DialogAction, DialogClose};\nuse gpui_component::WindowExt;\n```\n\n## Usage\n\n### Setup Application Root View\n\nLike Dialog, you need to set up your application's root view to render the dialog layer. See [Dialog documentation](./dialog.md#setup-application-root-view) for details.\n\n### Basic AlertDialog (Declarative API)\n\nCreate a fully declarative AlertDialog using `trigger` and `content`:\n\n```rust\nuse gpui_component::dialog::{AlertDialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter};\n\nAlertDialog::new(cx)\n    .trigger(\n        Button::new(\"show-alert\")\n            .outline()\n            .label(\"Show Alert\")\n    )\n    .content(|content, _, cx| {\n        content\n            .child(\n                DialogHeader::new()\n                    .child(DialogTitle::new().child(\"Are you absolutely sure?\"))\n                    .child(DialogDescription::new().child(\n                        \"This action cannot be undone. \\\n                        This will permanently delete your account from our servers.\"\n                    ))\n            )\n            .child(\n                DialogFooter::new()\n                    .child(\n                        Button::new(\"cancel\")\n                            .outline()\n                            .label(\"Cancel\")\n                            .on_click(|_, window, cx| {\n                                window.close_dialog(cx);\n                            })\n                    )\n                    .child(\n                        Button::new(\"ok\")\n                            .primary()\n                            .label(\"Continue\")\n                            .on_click(|_, window, cx| {\n                                window.push_notification(\"Confirmed\", cx);\n                                window.close_dialog(cx);\n                            })\n                    )\n            )\n    })\n```\n\n### Using DialogAction and DialogClose\n\n`DialogAction` and `DialogClose` are wrapper components that simplify button click handling by automatically triggering the appropriate actions:\n\n- **DialogClose**: Wraps a button to trigger the `Cancel` action, invoking `on_cancel` callback\n- **DialogAction**: Wraps a button to trigger the `Confirm` action, invoking `on_ok` callback\n\nThese components eliminate the need to manually call `window.close_dialog(cx)`:\n\n```rust\nAlertDialog::new(cx)\n    .trigger(Button::new(\"show-alert\").outline().label(\"Show Alert\"))\n    .on_ok(|_, window, cx| {\n        window.push_notification(\"You confirmed!\", cx);\n        true  // Return true to close dialog\n    })\n    .on_cancel(|_, window, cx| {\n        window.push_notification(\"You cancelled!\", cx);\n        true\n    })\n    .content(|content, _, cx| {\n        content\n            .child(\n                DialogHeader::new()\n                    .child(DialogTitle::new().child(\"Confirm Action\"))\n                    .child(DialogDescription::new().child(\"Do you want to proceed?\"))\n            )\n            .child(\n                DialogFooter::new()\n                    .child(\n                        DialogClose::new().child(\n                            Button::new(\"cancel\").outline().label(\"Cancel\")\n                        )\n                    )\n                    .child(\n                        DialogAction::new().child(\n                            Button::new(\"ok\").primary().label(\"Confirm\")\n                        )\n                    )\n            )\n    })\n```\n\n**Benefits:**\n- No need to manually close the dialog\n- Automatically connects to `on_ok` and `on_cancel` callbacks\n- Cleaner, more declarative code\n- Supports returning `false` from callbacks to prevent closing\n\n### Basic AlertDialog (Imperative API)\n\nOpen a dialog imperatively using `WindowExt::open_alert_dialog`:\n\n```rust\nwindow.open_alert_dialog(cx, |alert, _, _| {\n    alert\n        .title(\"Delete File\")\n        .description(\"Are you sure you want to delete this file? This action cannot be undone.\")\n        .show_cancel(true)\n        .on_ok(|_, window, cx| {\n            window.push_notification(\"File deleted\", cx);\n            true // Return true to close dialog\n        })\n})\n```\n\n### Custom Button Props\n\nUse `button_props` to customize button text and styles:\n\n```rust\nuse gpui_component::dialog::DialogButtonProps;\nuse gpui_component::button::ButtonVariant;\n\nwindow.open_alert_dialog(cx, |alert, _, _| {\n    alert\n        .title(\"Delete Account\")\n        .description(\"This will permanently delete your account and all associated data.\")\n        .button_props(\n            DialogButtonProps::default()\n                .ok_text(\"Delete\")\n                .ok_variant(ButtonVariant::Danger)\n                .cancel_text(\"Keep\")\n                .show_cancel(true)\n        )\n        .on_ok(|_, window, cx| {\n            window.push_notification(\"Account deleted\", cx);\n            true\n        })\n})\n```\n\n### AlertDialog with Icon\n\nUsing icon in declarative API:\n\n```rust\nuse gpui_component::{Icon, IconName, ActiveTheme};\n\nAlertDialog::new(cx)\n    .w(px(320.))\n    .trigger(Button::new(\"permission\").outline().label(\"Request Permission\"))\n    .on_ok(|_, window, cx| {\n        window.push_notification(\"Permission granted\", cx);\n        true\n    })\n    .content(|content, _, cx| {\n        content\n            .child(\n                DialogHeader::new()\n                    .items_center()\n                    .child(\n                        Icon::new(IconName::TriangleAlert)\n                            .size_10()\n                            .text_color(cx.theme().warning)\n                    )\n                    .child(DialogTitle::new().child(\"Network Permission Required\"))\n                    .child(DialogDescription::new().child(\n                        \"We need your permission to access the network to provide better services.\"\n                    ))\n            )\n            .child(\n                DialogFooter::new()\n                    .v_flex()\n                    .child(\n                        DialogAction::new().child(\n                            Button::new(\"allow\").w_full().primary().label(\"Allow\")\n                        )\n                    )\n                    .child(\n                        DialogClose::new().child(\n                            Button::new(\"deny\").w_full().outline().label(\"Don't Allow\")\n                        )\n                    )\n            )\n    })\n```\n\nUsing icon in imperative API:\n\n```rust\nwindow.open_alert_dialog(cx, |alert, _, cx| {\n    alert\n        .title(\"Warning\")\n        .description(\"This action requires confirmation.\")\n        .icon(\n            Icon::new(IconName::AlertTriangle)\n                .size_8()\n                .text_color(cx.theme().warning)\n        )\n})\n```\n\n### Destructive Action Confirmation\n\n```rust\nAlertDialog::new(cx)\n    .trigger(\n        Button::new(\"delete-account\")\n            .outline()\n            .danger()\n            .label(\"Delete Account\")\n    )\n    .on_ok(|_, window, cx| {\n        window.push_notification(\"Account deletion initiated\", cx);\n        true\n    })\n    .content(|content, _, _| {\n        content\n            .child(\n                DialogHeader::new()\n                    .child(DialogTitle::new().child(\"Delete Account\"))\n                    .child(DialogDescription::new().child(\n                        \"This will permanently delete your account \\\n                        and all associated data. This action cannot be undone.\"\n                    ))\n            )\n            .child(\n                DialogFooter::new()\n                    .child(\n                        DialogClose::new().child(\n                            Button::new(\"cancel\").flex_1().outline().label(\"Cancel\")\n                        )\n                    )\n                    .child(\n                        DialogAction::new().child(\n                            Button::new(\"delete\")\n                                .flex_1()\n                                .outline()\n                                .danger()\n                                .label(\"Delete Forever\")\n                        )\n                    )\n            )\n    })\n```\n\n### Custom Width\n\n```rust\nAlertDialog::new(cx)\n    .width(px(500.))\n    .trigger(Button::new(\"custom-width\").label(\"Custom Width\"))\n    .content(|content, _, _| {\n        // ... dialog content\n    })\n```\n\n### Controlling Dialog Close Behavior\n\n#### Allow Overlay Click to Close\n\n```rust\nwindow.open_alert_dialog(cx, |alert, _, _| {\n    alert\n        .title(\"Notice\")\n        .description(\"Click outside this dialog or press ESC to close it.\")\n        .overlay_closable(true)\n})\n```\n\n#### Disable Keyboard ESC to Close\n\n```rust\nwindow.open_alert_dialog(cx, |alert, _, _| {\n    alert\n        .title(\"Important Notice\")\n        .description(\"Please read this carefully before proceeding.\")\n        .keyboard(false)\n})\n```\n\n#### Show Close Button\n\n```rust\nwindow.open_alert_dialog(cx, |alert, _, _| {\n    alert\n        .title(\"Information\")\n        .description(\"Some information...\")\n        .close_button(true)\n})\n```\n\n### Prevent Dialog from Closing\n\nReturn `false` from `on_ok` or `on_cancel` callbacks to prevent the dialog from closing:\n\n```rust\nuse gpui_component::dialog::DialogButtonProps;\n\nwindow.open_alert_dialog(cx, |alert, _, _| {\n    alert\n        .title(\"Processing\")\n        .description(\"A process is running. Click Continue to stop it or Cancel to keep waiting.\")\n        .button_props(\n            DialogButtonProps::default()\n                .ok_text(\"Continue\")\n                .show_cancel(true)\n        )\n        .on_ok(|_, window, cx| {\n            // Return false to prevent closing\n            window.push_notification(\"Cannot close: Process still running\", cx);\n            false\n        })\n        .on_cancel(|_, window, cx| {\n            window.push_notification(\"Waiting...\", cx);\n            false\n        })\n})\n```\n\n### Dialog Close Callback\n\nUse `on_close` to execute actions after the dialog closes (called after `on_ok` or `on_cancel`):\n\n```rust\nwindow.open_alert_dialog(cx, |alert, _, _| {\n    alert\n        .title(\"Confirm\")\n        .description(\"Are you sure?\")\n        .on_close(|_, window, cx| {\n            window.push_notification(\"Dialog closed\", cx);\n        })\n})\n```\n\n## API Reference\n\n### AlertDialog\n\n| Method                   | Description                                                   |\n| ------------------------ | ------------------------------------------------------------- |\n| `new(cx)`                | Create a new AlertDialog                                      |\n| `trigger(element)`       | Set trigger element that opens the dialog when clicked        |\n| `content(builder)`       | Set dialog content using a builder function (declarative API) |\n| `title(title)`           | Set dialog title (imperative API)                             |\n| `description(desc)`      | Set dialog description (imperative API)                       |\n| `icon(icon)`             | Set dialog icon (imperative API)                              |\n| `button_props(props)`    | Set button properties (text, style, visibility)               |\n| `show_cancel(bool)`      | Show/hide cancel button, default `false`                      |\n| `width(px)`              | Set dialog width, default `420px`                             |\n| `overlay_closable(bool)` | Allow clicking overlay to close, default `false`              |\n| `close_button(bool)`     | Show/hide close button, default `false`                       |\n| `keyboard(bool)`         | Support ESC key to close, default `true`                      |\n| `on_ok(callback)`        | Set OK button callback, return `true` to close dialog         |\n| `on_cancel(callback)`    | Set cancel button callback, return `true` to close dialog     |\n| `on_close(callback)`     | Set callback after dialog closes                              |\n\n### DialogButtonProps\n\n| Method                    | Description                              |\n| ------------------------- | ---------------------------------------- |\n| `ok_text(text)`           | Set OK button text, default \"OK\"         |\n| `cancel_text(text)`       | Set cancel button text, default \"Cancel\" |\n| `ok_variant(variant)`     | Set OK button variant                    |\n| `cancel_variant(variant)` | Set cancel button variant                |\n| `show_cancel(bool)`       | Show/hide cancel button                  |\n| `on_ok(callback)`         | Set OK callback                          |\n| `on_cancel(callback)`     | Set cancel callback                      |\n\n### DialogAction\n\nA wrapper component that automatically triggers the `Confirm` action when its child element is clicked. This invokes the `on_ok` callback set on the AlertDialog.\n\n**Usage:**\n```rust\nDialogAction::new().child(\n    Button::new(\"ok\").primary().label(\"Confirm\")\n)\n```\n\n**Behavior:**\n- Dispatches `Confirm` action on click\n- Invokes the `on_ok` callback\n- Dialog closes if callback returns `true`\n- Dialog stays open if callback returns `false`\n\n### DialogClose\n\nA wrapper component that automatically triggers the `Cancel` action when its child element is clicked. This invokes the `on_cancel` callback set on the AlertDialog.\n\n**Usage:**\n```rust\nDialogClose::new().child(\n    Button::new(\"cancel\").outline().label(\"Cancel\")\n)\n```\n\n**Behavior:**\n- Dispatches `Cancel` action on click\n- Invokes the `on_cancel` callback\n- Dialog closes if callback returns `true` (or if no callback is set)\n- Dialog stays open if callback returns `false`\n\n## Examples\n\n### Delete Confirmation\n\nUsing imperative API with button props:\n\n```rust\nButton::new(\"delete\")\n    .danger()\n    .label(\"Delete\")\n    .on_click(|_, window, cx| {\n        window.open_alert_dialog(cx, |alert, _, _| {\n            alert\n                .title(\"Delete File?\")\n                .description(\"This action cannot be undone.\")\n                .button_props(\n                    DialogButtonProps::default()\n                        .ok_text(\"Delete\")\n                        .ok_variant(ButtonVariant::Danger)\n                        .show_cancel(true)\n                )\n                .on_ok(|_, window, cx| {\n                    // Perform delete operation\n                    window.push_notification(\"File deleted\", cx);\n                    true\n                })\n        });\n    })\n```\n\nOr using declarative API with DialogAction/DialogClose:\n\n```rust\nAlertDialog::new(cx)\n    .trigger(Button::new(\"delete\").danger().label(\"Delete\"))\n    .on_ok(|_, window, cx| {\n        window.push_notification(\"File deleted\", cx);\n        true\n    })\n    .content(|content, _, cx| {\n        content\n            .child(\n                DialogHeader::new()\n                    .child(DialogTitle::new().child(\"Delete File?\"))\n                    .child(DialogDescription::new().child(\"This action cannot be undone.\"))\n            )\n            .child(\n                DialogFooter::new()\n                    .child(\n                        DialogClose::new().child(\n                            Button::new(\"cancel\").outline().label(\"Cancel\")\n                        )\n                    )\n                    .child(\n                        DialogAction::new().child(\n                            Button::new(\"delete-confirm\").danger().label(\"Delete\")\n                        )\n                    )\n            )\n    })\n```\n\n### Session Timeout\n\n```rust\nwindow.open_alert_dialog(cx, |alert, _, _| {\n    alert\n        .content(|content, _, _| {\n            content\n                .child(\n                    DialogHeader::new()\n                        .items_center()\n                        .child(DialogTitle::new().child(\"Session Expired\"))\n                        .child(DialogDescription::new().child(\n                            \"Your session has expired due to inactivity. \\\n                            Please log in again to continue.\"\n                        ))\n                )\n                .child(\n                    DialogFooter::new()\n                        .child(\n                            Button::new(\"sign-in\")\n                                .label(\"Sign in\")\n                                .primary()\n                                .flex_1()\n                                .on_click(|_, window, cx| {\n                                    window.push_notification(\"Redirecting to login...\", cx);\n                                    window.close_dialog(cx);\n                                })\n                        )\n                )\n        })\n})\n```\n\n### Update Available\n\n```rust\nAlertDialog::new(cx)\n    .trigger(Button::new(\"update\").outline().label(\"Update Available\"))\n    .on_cancel(|_, window, cx| {\n        window.push_notification(\"Update postponed\", cx);\n        true\n    })\n    .on_ok(|_, window, cx| {\n        window.push_notification(\"Starting update...\", cx);\n        true\n    })\n    .content(|content, _, _| {\n        content\n            .child(\n                DialogHeader::new()\n                    .child(DialogTitle::new().child(\"Update Available\"))\n                    .child(DialogDescription::new().child(\n                        \"A new version (v2.0.0) is available. \\\n                        This update includes new features and bug fixes.\"\n                    ))\n            )\n            .child(\n                DialogFooter::new()\n                    .child(\n                        DialogClose::new().child(\n                            Button::new(\"later\").flex_1().outline().label(\"Later\")\n                        )\n                    )\n                    .child(\n                        DialogAction::new().child(\n                            Button::new(\"update-now\").flex_1().primary().label(\"Update Now\")\n                        )\n                    )\n            )\n    })\n```\n\n## Best Practices\n\n1. **Choose the Right API**: Use imperative API (`open_alert_dialog`) for simple confirmations; use declarative API (`trigger` + `content`) for complex layouts or integration with other components\n2. **Use DialogAction and DialogClose**: Prefer wrapping buttons with `DialogAction` and `DialogClose` over manual `window.close_dialog()` calls for cleaner, more declarative code\n3. **Clarify Intent**: Use appropriate button variants (e.g., `ButtonVariant::Danger` for delete operations) to communicate the importance of actions\n4. **Provide Clear Descriptions**: Ensure users understand the consequences of their actions, especially for destructive operations\n5. **Use Icons Wisely**: Icons can enhance attention for warnings and errors, but use them appropriately\n6. **Prevent Closing Carefully**: Only prevent dialog closing when user confirmation is truly necessary (e.g., a process is running)\n7. **Maintain Consistency**: Keep dialog button order and styles consistent throughout your application\n\n## Related Components\n\n- [Dialog] - More flexible dialog component\n- [DialogHeader] - Dialog header component\n- [DialogTitle] - Dialog title component\n- [DialogDescription] - Dialog description component\n- [DialogFooter] - Dialog footer component\n- [DialogAction] - Wrapper component for confirm/OK buttons\n- [DialogClose] - Wrapper component for cancel/close buttons\n\n[AlertDialog]: https://docs.rs/gpui-component/latest/gpui_component/dialog/struct.AlertDialog.html\n[Dialog]: https://docs.rs/gpui-component/latest/gpui_component/dialog/struct.Dialog.html\n[DialogHeader]: https://docs.rs/gpui-component/latest/gpui_component/dialog/struct.DialogHeader.html\n[DialogTitle]: https://docs.rs/gpui-component/latest/gpui_component/dialog/struct.DialogTitle.html\n[DialogDescription]: https://docs.rs/gpui-component/latest/gpui_component/dialog/struct.DialogDescription.html\n[DialogFooter]: https://docs.rs/gpui-component/latest/gpui_component/dialog/struct.DialogFooter.html\n[DialogAction]: https://docs.rs/gpui-component/latest/gpui_component/dialog/struct.DialogAction.html\n[DialogClose]: https://docs.rs/gpui-component/latest/gpui_component/dialog/struct.DialogClose.html\n"
  },
  {
    "path": "docs/docs/components/alert.md",
    "content": "---\ntitle: Alert\ndescription: Displays a callout for user attention.\n---\n\n# Alert\n\nA versatile alert component for displaying important messages to users. Supports multiple variants (info, success, warning, error), custom icons, optional titles, closable functionality, and banner mode. Perfect for notifications, status messages, and user feedback.\n\n## Import\n\n```rust\nuse gpui_component::alert::Alert;\n```\n\n## Usage\n\n### Basic Alert\n\n```rust\nAlert::new(\"alert-id\", \"This is a basic alert message.\")\n```\n\n### Alert with Title\n\n```rust\nAlert::new(\"alert-with-title\", \"Your changes have been saved successfully.\")\n    .title(\"Success!\")\n```\n\n### Alert Variants\n\n```rust\n// Info alert (blue)\nAlert::info(\"info-alert\", \"This is an informational message.\")\n    .title(\"Information\")\n\n// Success alert (green)\nAlert::success(\"success-alert\", \"Your operation completed successfully.\")\n    .title(\"Success!\")\n\n// Warning alert (yellow/orange)\nAlert::warning(\"warning-alert\", \"Please review your settings before proceeding.\")\n    .title(\"Warning\")\n\n// Error alert (red)\nAlert::error(\"error-alert\", \"An error occurred while processing your request.\")\n    .title(\"Error\")\n```\n\n### Alert Sizes\n\n```rust\nuse gpui_component::{alert::Alert, Sizable as _};\n\nAlert::info(\"alert\", \"Message content\")\n    .xsmall()\n    .title(\"XSmall Alert\")\nAlert::info(\"alert\", \"Message content\")\n    .small()\n    .title(\"Small Alert\")\n\nAlert::info(\"alert\", \"Message content\")\n    .title(\"Medium Alert\")\n\nAlert::info(\"alert\", \"Message content\")\n    .large()\n    .title(\"Large Alert\")\n```\n\n### Closable Alerts\n\nWhen you add an `on_close` handler, a close button appears on the alert:\n\n```rust\nAlert::info(\"closable-alert\", \"This alert can be dismissed.\")\n    .title(\"Dismissible\")\n    .on_close(|_event, _window, _cx| {\n        println!(\"Alert was closed\");\n        // Handle alert dismissal\n    })\n```\n\n### Banner Mode\n\nBanner alerts take full width and don't display titles:\n\n```rust\nAlert::info(\"banner-alert\", \"This is a banner alert that spans the full width.\")\n    .banner()\n\nAlert::success(\"banner-success\", \"Operation completed successfully!\")\n    .banner()\n\nAlert::warning(\"banner-warning\", \"System maintenance scheduled for tonight.\")\n    .banner()\n\nAlert::error(\"banner-error\", \"Service temporarily unavailable.\")\n    .banner()\n```\n\n### Custom Icons\n\n```rust\nuse gpui_component::IconName;\n\nAlert::new(\"custom-icon\", \"Meeting scheduled for tomorrow at 3 PM.\")\n    .title(\"Calendar Reminder\")\n    .icon(IconName::Calendar)\n```\n\n### With Markdown Content\n\nWe can use `TextView` to render formatted (Markdown or HTML) text within the alert,\nfor displaying lists, bold text, links, etc.\n\n```rust\nuse gpui_component::text::markdown;\n\nAlert::error(\n    \"error-with-markdown\",\n    markdown(\n        \"Please verify your billing information and try again.\\n\\\n        - Check your card details\\n\\\n        - Ensure sufficient funds\\n\\\n        - Verify billing address\"\n    ),\n)\n.title(\"Payment Failed\")\n```\n\n### Conditional Visibility\n\n```rust\nAlert::info(\"conditional-alert\", \"This alert may be hidden.\")\n    .title(\"Conditional\")\n    .visible(should_show_alert) // boolean condition\n```\n\n## API Reference\n\n- [Alert]\n\n## Examples\n\n### Form Validation Errors\n\n```rust\nAlert::error(\n    \"validation-error\",\n    \"Please correct the following errors before submitting:\\n\\\n    - Email address is required\\n\\\n    - Password must be at least 8 characters\\n\\\n    - Terms of service must be accepted\"\n)\n.title(\"Validation Failed\")\n```\n\n### Success Notification\n\n```rust\nAlert::success(\"save-success\", \"Your profile has been updated successfully.\")\n    .title(\"Changes Saved\")\n    .on_close(|_, _, _| {\n        // Auto-dismiss after showing\n    })\n```\n\n### System Status Banner\n\n```rust\nAlert::warning(\n    \"maintenance-banner\",\n    \"Scheduled maintenance will occur tonight from 2:00 AM to 4:00 AM EST. \\\n    Some services may be temporarily unavailable.\"\n)\n.banner()\n.large()\n```\n\n### Interactive Alert with Custom Action\n\n```rust\nAlert::info(\"update-available\", \"A new version of the application is available.\")\n    .title(\"Update Available\")\n    .icon(IconName::Download)\n    .on_close(cx.listener(|this, _, _, cx| {\n        // Handle update or dismiss\n        this.handle_update_notification(cx);\n    }))\n```\n\n### Multi-line Content with Formatting\n\n```rust\nuse gpui_component::text::markdown;\n\nAlert::warning(\n    \"security-alert\",\n    markdown(\n        \"**Security Notice**: Unusual activity detected on your account.\\n\\n\\\n        Recent activity:\\n\\\n        - Login from new device (Chrome on Windows)\\n\\\n        - Location: San Francisco, CA\\n\\\n        - Time: Today at 2:30 PM\\n\\n\\\n        If this wasn't you, please [change your password](/) immediately.\"\n    )\n)\n.title(\"Security Alert\")\n.icon(IconName::Shield)\n```\n\n[Alert]: https://docs.rs/gpui-component/latest/gpui_component/alert/struct.Alert.html\n"
  },
  {
    "path": "docs/docs/components/avatar.md",
    "content": "---\ntitle: Avatar\ndescription: Displays a user avatar image with fallback options.\n---\n\n# Avatar\n\nThe Avatar component displays user profile images with intelligent fallbacks. When no image is provided, it shows user initials or a placeholder icon. The component supports various sizes and can be grouped together for team displays.\n\n## Import\n\n```rust\nuse gpui_component::avatar::{Avatar, AvatarGroup};\n```\n\n## Usage\n\n### Basic Avatar\n\nYou can create an [Avatar] by providing an image source URL and a user name:\n\n```rust\nAvatar::new()\n    .name(\"John Doe\")\n    .src(\"https://example.com/avatar.jpg\")\n```\n\n### Avatar with Fallback Text\n\nWhen no image source is provided, the Avatar displays user initials with an automatically generated color background:\n\n```rust\n// Shows \"JD\" initials with colored background\nAvatar::new()\n    .name(\"John Doe\")\n\n// Shows \"JS\" initials\nAvatar::new()\n    .name(\"Jane Smith\")\n```\n\n### Avatar Placeholder\n\nFor anonymous users or when no name is provided:\n\n```rust\nuse gpui_component::IconName;\n\n// Default user icon placeholder\nAvatar::new()\n\n// Custom placeholder icon\nAvatar::new()\n    .placeholder(IconName::Building2)\n```\n\n### Avatar Sizes\n\n```rust\nAvatar::new()\n    .name(\"John Doe\")\n    .xsmall()\n\nAvatar::new()\n    .name(\"John Doe\")\n    .small()\n\nAvatar::new()\n    .name(\"John Doe\")   // 48px (default medium)\n\nAvatar::new()\n    .name(\"John Doe\")\n    .large()\n\n// Custom size\nAvatar::new()\n    .name(\"John Doe\")\n    .with_size(px(100.))\n```\n\n### Custom Styling\n\n```rust\nAvatar::new()\n    .src(\"https://example.com/avatar.jpg\")\n    .with_size(px(100.))\n    .border_3()\n    .border_color(cx.theme().foreground)\n    .shadow_sm()\n    .rounded(px(20.))  // Custom border radius\n```\n\n## AvatarGroup\n\nThe [AvatarGroup] component allows you to display multiple avatars in a compact, overlapping layout:\n\n### Basic Group\n\n```rust\nAvatarGroup::new()\n    .child(Avatar::new().src(\"https://example.com/user1.jpg\"))\n    .child(Avatar::new().src(\"https://example.com/user2.jpg\"))\n    .child(Avatar::new().src(\"https://example.com/user3.jpg\"))\n    .child(Avatar::new().name(\"John Doe\"))\n```\n\n### Group with Limit\n\n```rust\nAvatarGroup::new()\n    .limit(3)  // Show maximum 3 avatars\n    .child(Avatar::new().src(\"https://example.com/user1.jpg\"))\n    .child(Avatar::new().src(\"https://example.com/user2.jpg\"))\n    .child(Avatar::new().src(\"https://example.com/user3.jpg\"))\n    .child(Avatar::new().src(\"https://example.com/user4.jpg\"))  // Hidden\n    .child(Avatar::new().src(\"https://example.com/user5.jpg\"))  // Hidden\n```\n\n### Group with Ellipsis\n\nShow an ellipsis indicator when avatars are hidden due to the limit.\n\nIn this example, only 3 avatars are shown, and \"...\" indicates there are more:\n\n```rust\nAvatarGroup::new()\n    .limit(3)\n    .ellipsis()  // Shows \"...\" when limit is exceeded\n    .child(Avatar::new().src(\"https://example.com/user1.jpg\"))\n    .child(Avatar::new().src(\"https://example.com/user2.jpg\"))\n    .child(Avatar::new().src(\"https://example.com/user3.jpg\"))\n    .child(Avatar::new().src(\"https://example.com/user4.jpg\"))\n    .child(Avatar::new().src(\"https://example.com/user5.jpg\"))\n```\n\n### Group Sizes\n\nThe [Sizeable] trait can also be applied to AvatarGroup, and it will set the size for all contained avatars.\n\n```rust\n// Extra small group\nAvatarGroup::new()\n    .xsmall()\n    .child(Avatar::new().name(\"A\"))\n    .child(Avatar::new().name(\"B\"))\n    .child(Avatar::new().name(\"C\"))\n\n// Small group\nAvatarGroup::new()\n    .small()\n    .child(Avatar::new().name(\"A\"))\n    .child(Avatar::new().name(\"B\"))\n\n// Medium group (default)\nAvatarGroup::new()\n    .child(Avatar::new().name(\"A\"))\n    .child(Avatar::new().name(\"B\"))\n\n// Large group\nAvatarGroup::new()\n    .large()\n    .child(Avatar::new().name(\"A\"))\n    .child(Avatar::new().name(\"B\"))\n```\n\n### Adding Multiple Avatars\n\n```rust\nlet avatars = vec![\n    Avatar::new().src(\"https://example.com/user1.jpg\"),\n    Avatar::new().src(\"https://example.com/user2.jpg\"),\n    Avatar::new().name(\"John Doe\"),\n];\n\nAvatarGroup::new()\n    .children(avatars)\n    .limit(5)\n    .ellipsis()\n```\n\n## API Reference\n\n- [Avatar]\n- [AvatarGroup]\n\n## Examples\n\n### Team Display\n\n```rust\nuse gpui_component::{h_flex, v_flex};\n\nv_flex()\n    .gap_4()\n    .child(\"Development Team\")\n    .child(\n        AvatarGroup::new()\n            .limit(4)\n            .ellipsis()\n            .child(Avatar::new().name(\"Alice Johnson\").src(\"https://example.com/alice.jpg\"))\n            .child(Avatar::new().name(\"Bob Smith\").src(\"https://example.com/bob.jpg\"))\n            .child(Avatar::new().name(\"Charlie Brown\"))\n            .child(Avatar::new().name(\"Diana Prince\"))\n            .child(Avatar::new().name(\"Eve Wilson\"))\n    )\n```\n\n### User Profile Header\n\n```rust\nh_flex()\n    .items_center()\n    .gap_4()\n    .child(\n        Avatar::new()\n            .src(\"https://example.com/profile.jpg\")\n            .name(\"John Doe\")\n            .large()\n            .border_2()\n            .border_color(cx.theme().primary)\n    )\n    .child(\n        v_flex()\n            .child(\"John Doe\")\n            .child(\"Software Engineer\")\n    )\n```\n\n### Anonymous User\n\n```rust\nuse gpui_component::IconName;\n\nAvatar::new()\n    .placeholder(IconName::UserCircle)\n    .medium()\n```\n\n### Avatar with Custom Colors\n\n```rust\n// The avatar automatically generates colors based on the name\n// Different names will get different colors from the color palette\nAvatar::new().name(\"Alice\")    // Gets one color\nAvatar::new().name(\"Bob\")      // Gets a different color\nAvatar::new().name(\"Charlie\")  // Gets another color\n```\n\n[Avatar]: https://docs.rs/gpui-component/latest/gpui_component/avatar/struct.Avatar.html\n[AvatarGroup]: https://docs.rs/gpui-component/latest/gpui_component/avatar/struct.AvatarGroup.html\n[Sizable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Sizable.html\n"
  },
  {
    "path": "docs/docs/components/badge.md",
    "content": "---\ntitle: Badge\ndescription: A red dot that indicates the number of unread messages, status, or other notifications.\n---\n\n# Badge\n\nA versatile badge component that can display counts, dots, or icons on elements. Perfect for indicating notifications, status, or other contextual information on avatars, icons, or other UI elements.\n\n## Import\n\n```rust\nuse gpui_component::badge::Badge;\n```\n\n## Usage\n\n### Badge with Count\n\nUse `count` to display a numeric badge, if the count is greater than zero (`> 0`) the badge will be shown, otherwise it will be hidden.\n\nThere is a default maximum count of `99`, any count above this will be displayed as `99+`. You can customize this maximum using the [max](https://docs.rs/gpui-component/latest/gpui_component/badge/struct.Badge.html#method.max) method.\n\n```rust\nBadge::new()\n    .count(3)\n    .child(Icon::new(IconName::Bell))\n```\n\n### Variants\n\n- Default: Displays a numeric count.\n- Dot: A small dot indicator, typically used for status.\n- Icon: Displays an icon instead of a number.\n\n```rust\n// Number badge (default)\nBadge::new()\n    .count(5)\n    .child(Avatar::new().src(\"https://example.com/avatar.jpg\"))\n\n// Dot badge\nBadge::new()\n    .dot()\n    .child(Icon::new(IconName::Inbox))\n\n// Icon badge\nBadge::new()\n    .icon(IconName::Check)\n    .child(Avatar::new().src(\"https://example.com/avatar.jpg\"))\n```\n\n### Badge Sizes\n\nThe Badge is also implemented with the [Sizable] trait, allowing you to set small, medium (default), or large sizes.\n\n```rust\n// Small badge\nBadge::new()\n    .small()\n    .count(1)\n    .child(Avatar::new().small())\n\n// Medium badge (default)\nBadge::new()\n    .count(5)\n    .child(Avatar::new())\n\n// Large badge\nBadge::new()\n    .large()\n    .count(10)\n    .child(Avatar::new().large())\n```\n\n### Badge Colors\n\n```rust\nuse gpui_component::ActiveTheme;\n\n// Custom colors\nBadge::new()\n    .count(3)\n    .color(cx.theme().blue)\n    .child(Avatar::new())\n\nBadge::new()\n    .icon(IconName::Star)\n    .color(cx.theme().yellow)\n    .child(Avatar::new())\n\nBadge::new()\n    .dot()\n    .color(cx.theme().green)\n    .child(Icon::new(IconName::Bell))\n```\n\n### Badge on Icons\n\n```rust\nuse gpui_component::{Icon, IconName};\n\n// Badge with count on icon\nBadge::new()\n    .count(3)\n    .child(Icon::new(IconName::Bell).large())\n\n// Badge with high count (shows max)\nBadge::new()\n    .count(103)\n    .child(Icon::new(IconName::Inbox).large())\n\n// Custom max count\nBadge::new()\n    .count(150)\n    .max(999)\n    .child(Icon::new(IconName::Mail))\n```\n\n### Badge on Avatars\n\n```rust\nuse gpui_component::avatar::Avatar;\n\n// Basic count badge\nBadge::new()\n    .count(5)\n    .child(Avatar::new().src(\"https://example.com/avatar.jpg\"))\n\n// Status badge with icon\nBadge::new()\n    .icon(IconName::Check)\n    .color(cx.theme().green)\n    .child(Avatar::new().src(\"https://example.com/avatar.jpg\"))\n\n// Online indicator with dot\nBadge::new()\n    .dot()\n    .color(cx.theme().green)\n    .child(Avatar::new().src(\"https://example.com/avatar.jpg\"))\n```\n\n### Complex Nested Badges\n\n```rust\n// Badge on badge for complex status\nBadge::new()\n    .count(212)\n    .large()\n    .child(\n        Badge::new()\n            .icon(IconName::Check)\n            .large()\n            .color(cx.theme().cyan)\n            .child(Avatar::new().large().src(\"https://example.com/avatar.jpg\"))\n    )\n\n// Multiple status indicators\nBadge::new()\n    .count(2)\n    .color(cx.theme().green)\n    .large()\n    .child(\n        Badge::new()\n            .icon(IconName::Star)\n            .large()\n            .color(cx.theme().yellow)\n            .child(Avatar::new().large().src(\"https://example.com/avatar.jpg\"))\n    )\n```\n\n## API Reference\n\n- [Badge]\n\n## Examples\n\n### Notification Indicators\n\n```rust\n// Unread messages\nBadge::new()\n    .count(12)\n    .child(Icon::new(IconName::Mail).large())\n\n// New notifications\nBadge::new()\n    .count(3)\n    .color(cx.theme().red)\n    .child(Icon::new(IconName::Bell).large())\n\n// High priority with custom max\nBadge::new()\n    .count(1234)\n    .max(999)\n    .color(cx.theme().orange)\n    .child(Icon::new(IconName::AlertTriangle))\n```\n\n### Status Indicators\n\n```rust\n// Online status\nBadge::new()\n    .dot()\n    .color(cx.theme().green)\n    .child(Avatar::new().src(\"https://example.com/user.jpg\"))\n\n// Verified status\nBadge::new()\n    .icon(IconName::CheckCircle)\n    .color(cx.theme().blue)\n    .child(Avatar::new().src(\"https://example.com/verified-user.jpg\"))\n\n// Warning status\nBadge::new()\n    .icon(IconName::AlertTriangle)\n    .color(cx.theme().yellow)\n    .child(Avatar::new().src(\"https://example.com/user.jpg\"))\n```\n\n### Different Badge Positions\n\n```rust\n// The badge automatically positions itself based on variant:\n// - Dot: top-right corner (small dot)\n// - Number: top-right with dynamic sizing\n// - Icon: bottom-right corner with border\n```\n\n### Count Formatting\n\n```rust\n// Numbers 1-99 show as-is\nBadge::new().count(5)    // Shows \"5\"\nBadge::new().count(99)   // Shows \"99\"\n\n// Numbers above max show with \"+\"\nBadge::new().count(100)  // Shows \"99+\" (default max)\nBadge::new().count(1000).max(999) // Shows \"999+\"\n\n// Zero count hides the badge\nBadge::new().count(0)    // Badge not visible\n```\n\n[Badge]: https://docs.rs/gpui_component/latest/gpui_component/badge/struct.Badge.html\n[Sizable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Sizable.html\n"
  },
  {
    "path": "docs/docs/components/button.md",
    "content": "---\ntitle: Button\ndescription: Displays a button or a component that looks like a button.\n---\n\n# Button\n\nThe [Button] element with multiple variants, sizes, and states. Supports icons, loading states, and can be grouped together.\n\n## Import\n\n```rust\nuse gpui_component::button::{Button, ButtonGroup};\n```\n\n## Usage\n\n### Basic Button\n\n```rust\nButton::new(\"my-button\")\n    .label(\"Click me\")\n    .on_click(|_, _, _| {\n        println!(\"Button clicked!\");\n    })\n```\n\n### Variants\n\n```rust\n// Primary button\nButton::new(\"btn-primary\").primary().label(\"Primary\")\n\n// Secondary button (default)\nButton::new(\"btn-secondary\").label(\"Secondary\")\n\n// Danger button\nButton::new(\"btn-danger\").danger().label(\"Delete\")\n\n// Warning button\nButton::new(\"btn-warning\").warning().label(\"Warning\")\n\n// Success button\nButton::new(\"btn-success\").success().label(\"Success\")\n\n// Info button\nButton::new(\"btn-info\").info().label(\"Info\")\n\n// Ghost button\nButton::new(\"btn-ghost\").ghost().label(\"Ghost\")\n\n// Link button\nButton::new(\"btn-link\").link().label(\"Link\")\n\n// Text button\nButton::new(\"btn-text\").text().label(\"Text\")\n```\n\n### Outline Buttons\n\nOutline style is not a variant itself, but can be combined with other variants.\n\n```rust\nButton::new(\"btn\").primary().outline().label(\"Primary Outline\")\nButton::new(\"btn\").danger().outline().label(\"Danger Outline\")\n```\n\n### Compact Button\n\nThe `compact` method reduces the padding of the button for a more condensed appearance.\n\n```rust\n// Compact (reduced padding)\nButton::new(\"btn\")\n    .label(\"Compact\")\n    .compact()\n```\n\n### Sizeable\n\nThe Button supports the [Sizable] trait for different sizes.\n\n```rust\nButton::new(\"btn\").xsmall().label(\"Extra Small\")\nButton::new(\"btn\").small().label(\"Small\")\nButton::new(\"btn\").label(\"Medium\") // default\nButton::new(\"btn\").large().label(\"Large\")\n```\n\n### With Icons\n\nThe `icon` method supports multiple types, allowing you to use different visual indicators:\n\n- **[Icon] / [IconName]** - Static icons for actions and visual cues\n- **[Spinner]** - Animated loading indicator for async operations\n- **[ProgressCircle]** - Circular progress indicator showing completion percentage\n\nAll icon types automatically adapt to the button's size and can be customized with colors and other properties.\n\n#### Icon Types\n\n```rust\nuse gpui_component::{Icon, IconName};\n\n// Using IconName (simplest)\nButton::new(\"btn\")\n    .icon(IconName::Check)\n    .label(\"Confirm\")\n\n// Using Icon with custom size\nButton::new(\"btn\")\n    .icon(Icon::new(IconName::Heart))\n    .label(\"Like\")\n\n// Icon only (no label)\nButton::new(\"btn\")\n    .icon(IconName::Search)\n```\n\n#### Spinner Icon\n\nUse a [Spinner] to indicate loading or processing state:\n\n```rust\nuse gpui_component::spinner::Spinner;\n\n// Basic spinner\nButton::new(\"btn\")\n    .icon(Spinner::new())\n    .label(\"Loading...\")\n\n// Spinner with custom color\nButton::new(\"btn\")\n    .icon(Spinner::new().color(cx.theme().blue))\n    .label(\"Processing\")\n\n// Spinner with icon\nButton::new(\"btn\")\n    .icon(Spinner::new().icon(IconName::LoaderCircle))\n    .label(\"Syncing\")\n```\n\n#### ProgressCircle Icon\n\nUse a [ProgressCircle] to show progress percentage:\n\n```rust\nuse gpui_component::progress::ProgressCircle;\n\n// Basic progress circle\nButton::new(\"btn\")\n    .icon(ProgressCircle::new(\"install-progress\").value(45.0))\n    .label(\"Installing...\")\n\n// Progress circle with custom color\nButton::new(\"btn\")\n    .primary()\n    .icon(\n        ProgressCircle::new(\"download-progress\")\n            .value(75.0)\n            .color(cx.theme().primary_foreground)\n    )\n    .label(\"Downloading\")\n\n// Different sizes\nButton::new(\"btn\")\n    .small()\n    .icon(ProgressCircle::new(\"progress-1\").value(60.0))\n    .label(\"Installing...\")\n\nButton::new(\"btn\")\n    .large()\n    .icon(ProgressCircle::new(\"progress-2\").value(80.0))\n    .label(\"Installing...\")\n```\n\n#### Dynamic Icon Updates\n\nIcons can be updated dynamically based on component state:\n\n```rust\nstruct InstallButton {\n    progress: f32,\n    is_installing: bool,\n}\n\nimpl InstallButton {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let button = Button::new(\"install-btn\")\n            .label(if self.is_installing {\n                \"Installing...\"\n            } else {\n                \"Install\"\n            });\n\n        if self.is_installing {\n            button.icon(\n                ProgressCircle::new(\"install-progress\")\n                    .value(self.progress)\n            )\n        } else {\n            button.icon(IconName::Download)\n        }\n    }\n}\n```\n\n#### Loading State with Icons\n\nWhen a button is in loading state, it automatically handles icon transitions:\n\n```rust\n// If icon is already a Spinner or ProgressCircle, it will be shown during loading\nButton::new(\"btn\")\n    .icon(Spinner::new())\n    .label(\"Processing\")\n    .loading(true) // Spinner will continue to show\n\n// If icon is a regular Icon, it will be replaced with a Spinner during loading\nButton::new(\"btn\")\n    .icon(IconName::Save)\n    .label(\"Saving\")\n    .loading(true) // Icon will be replaced with Spinner\n```\n\n### With a dropdown caret icon\n\nThe `.dropdown_caret` method can allows adding a dropdown caret icon to end of the button.\n\n```rust\nButton::new(\"btn\")\n    .label(\"Options\")\n    .dropdown_caret(true)\n```\n\n### Button States\n\nThere have `disabled`, `loading`, `selected` state for buttons to indicate different statuses.\n\n```rust\n// Disabled\nButton::new(\"btn\")\n    .label(\"Disabled\")\n    .disabled(true)\n\n// Loading\nButton::new(\"btn\")\n    .label(\"Loading\")\n    .loading(true)\n\n// Selected\nButton::new(\"btn\")\n    .label(\"Selected\")\n    .selected(true)\n```\n\n## Button Group\n\n```rust\nButtonGroup::new(\"btn-group\")\n    .child(Button::new(\"btn1\").label(\"One\"))\n    .child(Button::new(\"btn2\").label(\"Two\"))\n    .child(Button::new(\"btn3\").label(\"Three\"))\n```\n\n### Toggle Button Group\n\n```rust\nButtonGroup::new(\"toggle-group\")\n    .multiple(true) // Allow multiple selections\n    .child(Button::new(\"btn1\").label(\"Option 1\").selected(true))\n    .child(Button::new(\"btn2\").label(\"Option 2\"))\n    .child(Button::new(\"btn3\").label(\"Option 3\"))\n    .on_click(|selected_indices, _, _| {\n        println!(\"Selected: {:?}\", selected_indices);\n    })\n```\n\n## Custom Variant\n\n```rust\nuse gpui_component::button::ButtonCustomVariant;\n\nlet custom = ButtonCustomVariant::new(cx)\n    .color(cx.theme().magenta)\n    .foreground(cx.theme().primary_foreground)\n    .border(cx.theme().magenta)\n    .hover(cx.theme().magenta.opacity(0.1))\n    .active(cx.theme().magenta);\n\nButton::new(\"custom-btn\")\n    .custom(custom)\n    .label(\"Custom Button\")\n```\n\n## API Reference\n\n- [Button]\n- [ButtonGroup]\n- [ButtonCustomVariant]\n\n## Examples\n\n### With Tooltip\n\n```rust\nButton::new(\"btn\")\n    .label(\"Hover me\")\n    .tooltip(\"This is a helpful tooltip\")\n```\n\n### Custom Children\n\n```rust\nButton::new(\"btn\")\n    .child(\n        h_flex()\n            .items_center()\n            .gap_2()\n            .child(\"Custom Content\")\n            .child(IconName::ChevronDown)\n            .child(IconName::Eye)\n    )\n```\n\n[Button]: https://docs.rs/gpui-component/latest/gpui_component/button/struct.Button.html\n[ButtonGroup]: https://docs.rs/gpui-component/latest/gpui_component/button/struct.ButtonGroup.html\n[ButtonCustomVariant]: https://docs.rs/gpui-component/latest/gpui_component/button/struct.ButtonCustomVariant.html\n[Sizable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Sizable.html\n[Spinner]: https://docs.rs/gpui-component/latest/gpui_component/spinner/struct.Spinner.html\n[ProgressCircle]: https://docs.rs/gpui-component/latest/gpui_component/progress/struct.ProgressCircle.html\n[Icon]: https://docs.rs/gpui-component/latest/gpui_component/icon/struct.Icon.html\n[IconName]: https://docs.rs/gpui-component/latest/gpui_component/icon/enum.IconName.html\n"
  },
  {
    "path": "docs/docs/components/calendar.md",
    "content": "---\ntitle: Calendar\ndescription: A flexible calendar component for displaying months, navigating dates, and selecting single dates or date ranges.\n---\n\n# Calendar\n\nA standalone calendar component that provides a rich interface for date selection and navigation. The Calendar component supports single date selection, date range selection, multiple month views, custom disabled dates, and comprehensive keyboard navigation.\n\n- [CalendarState]: For managing calendar state and selection.\n- [Calendar]: For rendering the calendar UI.\n\n## Import\n\n```rust\nuse gpui_component::{\n    calendar::{Calendar, CalendarState, CalendarEvent, Date, Matcher},\n};\n```\n\n## Usage\n\n### Basic Calendar\n\n```rust\nlet state = cx.new(|cx| CalendarState::new(window, cx));\nCalendar::new(&state)\n```\n\n### Calendar with Initial Date\n\n```rust\nuse chrono::Local;\n\nlet state = cx.new(|cx| {\n    let mut state = CalendarState::new(window, cx);\n    state.set_date(Local::now().naive_local().date(), window, cx);\n    state\n});\n\nCalendar::new(&state)\n```\n\n### Date Range Calendar\n\n```rust\nuse chrono::{Local, Days};\n\nlet state = cx.new(|cx| {\n    let mut state = CalendarState::new(window, cx);\n    let now = Local::now().naive_local().date();\n    state.set_date(\n        Date::Range(Some(now), now.checked_add_days(Days::new(7))),\n        window,\n        cx\n    );\n    state\n});\n\nCalendar::new(&state)\n```\n\n### Multiple Months Display\n\n```rust\n// Show 2 months side by side\nCalendar::new(&state)\n    .number_of_months(2)\n\n// Show 3 months\nCalendar::new(&state)\n    .number_of_months(3)\n```\n\n### Calendar Sizes\n\n```rust\nCalendar::new(&state).large()\nCalendar::new(&state) // medium (default)\nCalendar::new(&state).small()\n```\n\n## Date Restrictions\n\n### Disabled Weekends\n\n```rust\nlet state = cx.new(|cx| {\n    CalendarState::new(window, cx)\n        .disabled_matcher(vec![0, 6]) // Sunday=0, Saturday=6\n});\n\nCalendar::new(&state)\n```\n\n### Disabled Specific Weekdays\n\n```rust\n// Disable Sundays, Wednesdays, and Saturdays\nlet state = cx.new(|cx| {\n    CalendarState::new(window, cx)\n        .disabled_matcher(vec![0, 3, 6])\n});\n\nCalendar::new(&state)\n```\n\n### Disabled Date Range\n\n```rust\nuse chrono::{Local, Days};\n\nlet now = Local::now().naive_local().date();\n\nlet state = cx.new(|cx| {\n    CalendarState::new(window, cx)\n        .disabled_matcher(Matcher::range(\n            Some(now),\n            now.checked_add_days(Days::new(7)),\n        ))\n});\n\nCalendar::new(&state)\n```\n\n### Disabled Date Interval\n\n```rust\n// Disable dates outside the interval (before/after specified dates)\nlet state = cx.new(|cx| {\n    CalendarState::new(window, cx)\n        .disabled_matcher(Matcher::interval(\n            Some(now.checked_sub_days(Days::new(30)).unwrap()),\n            now.checked_add_days(Days::new(30))\n        ))\n});\n\nCalendar::new(&state)\n```\n\n### Custom Disabled Dates\n\n```rust\n// Disable first 5 days of each month\nlet state = cx.new(|cx| {\n    CalendarState::new(window, cx)\n        .disabled_matcher(Matcher::custom(|date| {\n            date.day0() < 5 // day0() returns 0-based day\n        }))\n});\n\nCalendar::new(&state)\n\n// Disable all Mondays\nlet state = cx.new(|cx| {\n    CalendarState::new(window, cx)\n        .disabled_matcher(Matcher::custom(|date| {\n            date.weekday() == chrono::Weekday::Mon\n        }))\n});\n\nCalendar::new(&state)\n\n// Disable past dates\nlet state = cx.new(|cx| {\n    CalendarState::new(window, cx)\n        .disabled_matcher(Matcher::custom(|date| {\n            *date < Local::now().naive_local().date()\n        }))\n});\n\nCalendar::new(&state)\n```\n\n## Month/Year Navigation\n\nThe Calendar automatically provides navigation controls:\n\n- **Previous/Next Month**: Arrow buttons in the header\n- **Month Selection**: Click on month name to open month picker\n- **Year Selection**: Click on year to open year picker\n- **Year Pages**: Navigate through 20-year pages in year view\n\n### Custom Year Range\n\n```rust\nlet state = cx.new(|cx| {\n    CalendarState::new(window, cx)\n        .year_range((2020, 2030)) // Limit to specific year range\n});\n\nCalendar::new(&state)\n```\n\n## Handle Selection Events\n\n```rust\nlet state = cx.new(|cx| CalendarState::new(window, cx));\n\ncx.subscribe(&state, |view, _, event, _| {\n    match event {\n        CalendarEvent::Selected(date) => {\n            match date {\n                Date::Single(Some(selected_date)) => {\n                    println!(\"Date selected: {}\", selected_date);\n                }\n                Date::Range(Some(start), Some(end)) => {\n                    println!(\"Range selected: {} to {}\", start, end);\n                }\n                Date::Range(Some(start), None) => {\n                    println!(\"Range start: {}\", start);\n                }\n                _ => {\n                    println!(\"Selection cleared\");\n                }\n            }\n        }\n    }\n});\n\nCalendar::new(&state)\n```\n\n## Advanced Examples\n\n### Business Days Only Calendar\n\n```rust\nuse chrono::Weekday;\n\nlet state = cx.new(|cx| {\n    CalendarState::new(window, cx)\n        .disabled_matcher(Matcher::custom(|date| {\n            matches!(date.weekday(), Weekday::Sat | Weekday::Sun)\n        }))\n});\n\nCalendar::new(&state)\n```\n\n### Holiday Calendar\n\n```rust\nuse chrono::NaiveDate;\nuse std::collections::HashSet;\n\n// Define holidays\nlet holidays: HashSet<NaiveDate> = [\n    NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(), // New Year\n    NaiveDate::from_ymd_opt(2024, 7, 4).unwrap(), // Independence Day\n    NaiveDate::from_ymd_opt(2024, 12, 25).unwrap(), // Christmas\n].into_iter().collect();\n\nlet state = cx.new(|cx| {\n    CalendarState::new(window, cx)\n        .disabled_matcher(Matcher::custom(move |date| {\n            holidays.contains(date)\n        }))\n});\n\nCalendar::new(&state)\n```\n\n### Multi-Month Range Selector\n\n```rust\nlet state = cx.new(|cx| {\n    let mut state = CalendarState::new(window, cx);\n    state.set_date(Date::Range(None, None), window, cx); // Range mode\n    state\n});\n\nCalendar::new(&state)\n    .number_of_months(3) // Show 3 months for easier range selection\n```\n\n### Quarterly View Calendar\n\n```rust\nlet state = cx.new(|cx| CalendarState::new(window, cx));\n\n// Update to show current quarter's months\nCalendar::new(&state)\n    .number_of_months(3)\n```\n\n## Custom Styling\n\n```rust\nuse gpui::{px, relative};\n\nCalendar::new(&calendar)\n    .p_4() // Custom padding\n    .bg(cx.theme().secondary) // Custom background\n    .border_2() // Custom border\n    .border_color(cx.theme().primary) // Custom border color\n    .rounded(px(12.)) // Custom border radius\n    .w(px(400.)) // Custom width\n    .h(px(350.)) // Custom height\n```\n\n## API Reference\n\n- [Calendar]\n- [CalendarState]\n- [RangeMatcher]\n\n## Examples\n\n### Event Planning Calendar\n\n```rust\nlet event_calendar = cx.new(|cx| {\n    let mut state = CalendarState::new(window, cx);\n    // Disable past dates and weekends\n    state = state.disabled_matcher(Matcher::custom(|date| {\n        let now = Local::now().naive_local().date();\n        *date < now || matches!(date.weekday(), Weekday::Sat | Weekday::Sun)\n    }));\n    state\n});\n\nCalendar::new(&event_calendar)\n    .large() // Easier to see and interact with\n```\n\n### Vacation Booking Calendar\n\n```rust\nlet vacation_calendar = cx.new(|cx| {\n    let mut state = CalendarState::new(window, cx);\n    state.set_date(Date::Range(None, None), window, cx); // Range mode\n    state\n});\n\nCalendar::new(&vacation_calendar)\n    .number_of_months(2) // Show 2 months for range selection\n```\n\n### Report Date Range Selector\n\n```rust\nlet report_calendar = cx.new(|cx| {\n    let mut state = CalendarState::new(window, cx)\n        .year_range((2020, 2025)); // Limit to business years\n\n    state.set_date(Date::Range(None, None), window, cx);\n    state\n});\n\nCalendar::new(&report_calendar)\n    .number_of_months(3)\n    .small() // Compact for dashboard use\n```\n\n### Availability Calendar\n\n```rust\nuse std::collections::HashSet;\n\nlet unavailable_dates: HashSet<NaiveDate> = get_unavailable_dates();\n\nlet availability_calendar = cx.new(|cx| {\n    CalendarState::new(window, cx)\n        .disabled_matcher(Matcher::custom(move |date| {\n            unavailable_dates.contains(date)\n        }))\n});\n\nCalendar::new(&availability_calendar)\n    .number_of_months(2)\n```\n\nThe Calendar component provides a foundation for any date-related UI requirements, from simple date pickers to complex scheduling interfaces.\n\n[Calendar]: https://docs.rs/gpui-component/latest/gpui_component/calendar/struct.Calendar.html\n[CalendarState]: https://docs.rs/gpui-component/latest/gpui_component/calendar/struct.CalendarState.html\n[RangeMatcher]: https://docs.rs/gpui-component/latest/gpui_component/calendar/struct.RangeMatcher.html\n"
  },
  {
    "path": "docs/docs/components/chart.md",
    "content": "---\ntitle: Chart\ndescription: Beautiful charts and graphs for data visualization including line, bar, area, pie, and candlestick charts.\n---\n\n# Chart\n\nA comprehensive charting library providing Line, Bar, Area, Pie, and Candlestick charts for data visualization. The charts feature smooth animations, customizable styling, tooltips, legends, and automatic theming that adapts to your application's theme.\n\n## Import\n\n```rust\nuse gpui_component::chart::{LineChart, BarChart, AreaChart, PieChart, CandlestickChart};\n```\n\n## Chart Types\n\n### LineChart\n\nA line chart displays data points connected by straight line segments, perfect for showing trends over time.\n\n#### Basic Line Chart\n\n```rust\n#[derive(Clone)]\nstruct DataPoint {\n    x: String,\n    y: f64,\n}\n\nlet data = vec![\n    DataPoint { x: \"Jan\".to_string(), y: 100.0 },\n    DataPoint { x: \"Feb\".to_string(), y: 150.0 },\n    DataPoint { x: \"Mar\".to_string(), y: 120.0 },\n];\n\nLineChart::new(data)\n    .x(|d| d.x.clone())\n    .y(|d| d.y)\n```\n\n#### Line Chart Variants\n\n```rust\n// Basic curved line (default)\nLineChart::new(data)\n    .x(|d| d.month.clone())\n    .y(|d| d.value)\n\n// Linear interpolation\nLineChart::new(data)\n    .x(|d| d.month.clone())\n    .y(|d| d.value)\n    .linear()\n\n// Step after interpolation\nLineChart::new(data)\n    .x(|d| d.month.clone())\n    .y(|d| d.value)\n    .step_after()\n\n// With dots at data points\nLineChart::new(data)\n    .x(|d| d.month.clone())\n    .y(|d| d.value)\n    .dot()\n\n// Custom stroke color\nLineChart::new(data)\n    .x(|d| d.month.clone())\n    .y(|d| d.value)\n    .stroke(cx.theme().success)\n```\n\n#### Tick Control\n\n```rust\n// Show every tick\nLineChart::new(data)\n    .x(|d| d.month.clone())\n    .y(|d| d.value)\n    .tick_margin(1)\n\n// Show every 2nd tick\nLineChart::new(data)\n    .x(|d| d.month.clone())\n    .y(|d| d.value)\n    .tick_margin(2)\n```\n\n### BarChart\n\nA bar chart uses rectangular bars to show comparisons among categories.\n\n#### Basic Bar Chart\n\n```rust\nBarChart::new(data)\n    .x(|d| d.category.clone())\n    .y(|d| d.value)\n```\n\n#### Bar Chart Customization\n\n```rust\n// Custom fill colors\nBarChart::new(data)\n    .x(|d| d.category.clone())\n    .y(|d| d.value)\n    .fill(|d| d.color)\n\n// With labels on bars\nBarChart::new(data)\n    .x(|d| d.category.clone())\n    .y(|d| d.value)\n    .label(|d| format!(\"{}\", d.value))\n\n// Custom tick spacing\nBarChart::new(data)\n    .x(|d| d.category.clone())\n    .y(|d| d.value)\n    .tick_margin(2)\n```\n\n### AreaChart\n\nAn area chart displays quantitative data visually, similar to a line chart but with the area below the line filled.\n\n#### Basic Area Chart\n\n```rust\nAreaChart::new(data)\n    .x(|d| d.time.clone())\n    .y(|d| d.value)\n```\n\n#### Stacked Area Charts\n\n```rust\n// Multi-series area chart\nAreaChart::new(data)\n    .x(|d| d.date.clone())\n    .y(|d| d.desktop)  // First series\n    .stroke(cx.theme().chart_1)\n    .fill(cx.theme().chart_1.opacity(0.4))\n    .y(|d| d.mobile)   // Second series\n    .stroke(cx.theme().chart_2)\n    .fill(cx.theme().chart_2.opacity(0.4))\n```\n\n#### Area Chart Styling\n\n```rust\nuse gpui::{linear_gradient, linear_color_stop};\n\n// With gradient fill\nAreaChart::new(data)\n    .x(|d| d.month.clone())\n    .y(|d| d.value)\n    .fill(linear_gradient(\n        0.,\n        linear_color_stop(cx.theme().chart_1.opacity(0.4), 1.),\n        linear_color_stop(cx.theme().background.opacity(0.3), 0.),\n    ))\n\n// Different interpolation styles\nAreaChart::new(data)\n    .x(|d| d.month.clone())\n    .y(|d| d.value)\n    .linear()  // or .step_after()\n```\n\n### PieChart\n\nA pie chart displays data as slices of a circular chart, ideal for showing proportions.\n\n#### Basic Pie Chart\n\n```rust\nPieChart::new(data)\n    .value(|d| d.amount as f32)\n    .outer_radius(100.)\n```\n\n#### Donut Chart\n\n```rust\nPieChart::new(data)\n    .value(|d| d.amount as f32)\n    .outer_radius(100.)\n    .inner_radius(60.) // Creates donut effect\n```\n\n#### Pie Chart Customization\n\n```rust\n// Custom colors\nPieChart::new(data)\n    .value(|d| d.amount as f32)\n    .outer_radius(100.)\n    .color(|d| d.color)\n\n// With padding between slices\nPieChart::new(data)\n    .value(|d| d.amount as f32)\n    .outer_radius(100.)\n    .inner_radius(60.)\n    .pad_angle(4. / 100.) // 4% padding\n```\n\n### CandlestickChart\n\nA candlestick chart displays financial data using OHLC (Open, High, Low, Close) values, perfect for visualizing stock prices and market trends.\n\n#### Basic Candlestick Chart\n\n```rust\n#[derive(Clone)]\nstruct StockPrice {\n    pub date: String,\n    pub open: f64,\n    pub high: f64,\n    pub low: f64,\n    pub close: f64,\n}\n\nlet data = vec![\n    StockPrice { date: \"Jan\".to_string(), open: 100.0, high: 110.0, low: 95.0, close: 105.0 },\n    StockPrice { date: \"Feb\".to_string(), open: 105.0, high: 115.0, low: 100.0, close: 112.0 },\n    StockPrice { date: \"Mar\".to_string(), open: 112.0, high: 120.0, low: 108.0, close: 115.0 },\n];\n\nCandlestickChart::new(data)\n    .x(|d| d.date.clone())\n    .open(|d| d.open)\n    .high(|d| d.high)\n    .low(|d| d.low)\n    .close(|d| d.close)\n```\n\n#### Candlestick Chart Customization\n\n```rust\n// Adjust body width ratio (default: 0.6)\nCandlestickChart::new(data)\n    .x(|d| d.date.clone())\n    .open(|d| d.open)\n    .high(|d| d.high)\n    .low(|d| d.low)\n    .close(|d| d.close)\n    .body_width_ratio(0.4) // Narrower bodies\n\n// Custom tick spacing\nCandlestickChart::new(data)\n    .x(|d| d.date.clone())\n    .open(|d| d.open)\n    .high(|d| d.high)\n    .low(|d| d.low)\n    .close(|d| d.close)\n    .tick_margin(2) // Show every 2nd tick\n```\n\n#### Candlestick Chart Colors\n\nThe candlestick chart automatically uses theme colors:\n\n- **Bullish** (close > open): `bullish` color (green)\n- **Bearish** (close < open): `bearish` color (red)\n\n## Data Structures\n\n### Example Data Types\n\n```rust\n// Time series data\n#[derive(Clone)]\nstruct DailyDevice {\n    pub date: String,\n    pub desktop: f64,\n    pub mobile: f64,\n}\n\n// Category data with styling\n#[derive(Clone)]\nstruct MonthlyDevice {\n    pub month: String,\n    pub desktop: f64,\n    pub color_alpha: f32,\n}\n\nimpl MonthlyDevice {\n    pub fn color(&self, base_color: Hsla) -> Hsla {\n        base_color.alpha(self.color_alpha)\n    }\n}\n\n// Financial data\n#[derive(Clone)]\nstruct StockPrice {\n    pub date: String,\n    pub open: f64,\n    pub high: f64,\n    pub low: f64,\n    pub close: f64,\n    pub volume: u64,\n}\n```\n\n## Chart Configuration\n\n### Container Setup\n\n```rust\nfn chart_container(\n    title: &str,\n    chart: impl IntoElement,\n    center: bool,\n    cx: &mut Context<ChartStory>,\n) -> impl IntoElement {\n    v_flex()\n        .flex_1()\n        .h_full()\n        .border_1()\n        .border_color(cx.theme().border)\n        .rounded(cx.theme().radius_lg)\n        .p_4()\n        .child(\n            div()\n                .when(center, |this| this.text_center())\n                .font_semibold()\n                .child(title.to_string()),\n        )\n        .child(\n            div()\n                .when(center, |this| this.text_center())\n                .text_color(cx.theme().muted_foreground)\n                .text_sm()\n                .child(\"Data period label\"),\n        )\n        .child(div().flex_1().py_4().child(chart))\n        .child(\n            div()\n                .when(center, |this| this.text_center())\n                .font_semibold()\n                .text_sm()\n                .child(\"Summary statistic\"),\n        )\n        .child(\n            div()\n                .when(center, |this| this.text_center())\n                .text_color(cx.theme().muted_foreground)\n                .text_sm()\n                .child(\"Additional context\"),\n        )\n}\n```\n\n### Theme Integration\n\n```rust\n// Charts automatically use theme colors\nlet chart = LineChart::new(data)\n    .x(|d| d.date.clone())\n    .y(|d| d.value)\n    .stroke(cx.theme().chart_1); // Uses theme chart colors\n\n// Available theme chart colors:\n// cx.theme().chart_1\n// cx.theme().chart_2\n// cx.theme().chart_3\n// ... up to chart_5\n```\n\n## API Reference\n\n- [LineChart]\n- [BarChart]\n- [AreaChart]\n- [PieChart]\n- [CandlestickChart]\n\n## Examples\n\n### Sales Dashboard\n\n```rust\n#[derive(Clone)]\nstruct SalesData {\n    month: String,\n    revenue: f64,\n    profit: f64,\n    region: String,\n}\n\nfn sales_dashboard(data: Vec<SalesData>, cx: &mut Context<Self>) -> impl IntoElement {\n    v_flex()\n        .gap_4()\n        .child(\n            h_flex()\n                .gap_4()\n                .child(\n                    chart_container(\n                        \"Monthly Revenue\",\n                        LineChart::new(data.clone())\n                            .x(|d| d.month.clone())\n                            .y(|d| d.revenue)\n                            .stroke(cx.theme().chart_1)\n                            .dot(),\n                        false,\n                        cx,\n                    )\n                )\n                .child(\n                    chart_container(\n                        \"Profit Breakdown\",\n                        PieChart::new(data.clone())\n                            .value(|d| d.profit as f32)\n                            .outer_radius(80.)\n                            .color(|d| match d.region.as_str() {\n                                \"North\" => cx.theme().chart_1,\n                                \"South\" => cx.theme().chart_2,\n                                \"East\" => cx.theme().chart_3,\n                                \"West\" => cx.theme().chart_4,\n                                _ => cx.theme().chart_5,\n                            }),\n                        true,\n                        cx,\n                    )\n                )\n        )\n        .child(\n            chart_container(\n                \"Regional Performance\",\n                BarChart::new(data)\n                    .x(|d| d.region.clone())\n                    .y(|d| d.revenue)\n                    .fill(|d| match d.region.as_str() {\n                        \"North\" => cx.theme().chart_1,\n                        \"South\" => cx.theme().chart_2,\n                        \"East\" => cx.theme().chart_3,\n                        \"West\" => cx.theme().chart_4,\n                        _ => cx.theme().chart_5,\n                    })\n                    .label(|d| format!(\"${:.0}k\", d.revenue / 1000.)),\n                false,\n                cx,\n            )\n        )\n}\n```\n\n### Multi-Series Time Chart\n\n```rust\n#[derive(Clone)]\nstruct DeviceUsage {\n    date: String,\n    desktop: f64,\n    mobile: f64,\n    tablet: f64,\n}\n\nfn device_usage_chart(data: Vec<DeviceUsage>, cx: &mut Context<Self>) -> impl IntoElement {\n    chart_container(\n        \"Device Usage Over Time\",\n        AreaChart::new(data)\n            .x(|d| d.date.clone())\n            .y(|d| d.desktop)\n            .stroke(cx.theme().chart_1)\n            .fill(linear_gradient(\n                0.,\n                linear_color_stop(cx.theme().chart_1.opacity(0.4), 1.),\n                linear_color_stop(cx.theme().background.opacity(0.3), 0.),\n            ))\n            .y(|d| d.mobile)\n            .stroke(cx.theme().chart_2)\n            .fill(linear_gradient(\n                0.,\n                linear_color_stop(cx.theme().chart_2.opacity(0.4), 1.),\n                linear_color_stop(cx.theme().background.opacity(0.3), 0.),\n            ))\n            .y(|d| d.tablet)\n            .stroke(cx.theme().chart_3)\n            .fill(linear_gradient(\n                0.,\n                linear_color_stop(cx.theme().chart_3.opacity(0.4), 1.),\n                linear_color_stop(cx.theme().background.opacity(0.3), 0.),\n            ))\n            .tick_margin(3),\n        false,\n        cx,\n    )\n}\n```\n\n### Financial Chart\n\n```rust\n#[derive(Clone)]\nstruct StockData {\n    date: String,\n    price: f64,\n    volume: u64,\n}\n\n#[derive(Clone)]\nstruct StockOHLC {\n    date: String,\n    open: f64,\n    high: f64,\n    low: f64,\n    close: f64,\n}\n\nfn stock_chart(ohlc_data: Vec<StockOHLC>, price_data: Vec<StockData>, cx: &mut Context<Self>) -> impl IntoElement {\n    v_flex()\n        .gap_4()\n        .child(\n            chart_container(\n                \"Stock Price - Candlestick\",\n                CandlestickChart::new(ohlc_data.clone())\n                    .x(|d| d.date.clone())\n                    .open(|d| d.open)\n                    .high(|d| d.high)\n                    .low(|d| d.low)\n                    .close(|d| d.close)\n                    .tick_margin(3),\n                false,\n                cx,\n            )\n        )\n        .child(\n            chart_container(\n                \"Stock Price - Line\",\n                LineChart::new(price_data.clone())\n                    .x(|d| d.date.clone())\n                    .y(|d| d.price)\n                    .stroke(cx.theme().chart_1)\n                    .linear()\n                    .tick_margin(5),\n                false,\n                cx,\n            )\n        )\n        .child(\n            chart_container(\n                \"Trading Volume\",\n                BarChart::new(price_data)\n                    .x(|d| d.date.clone())\n                    .y(|d| d.volume as f64)\n                    .fill(|d| {\n                        if d.volume > 1000000 {\n                            cx.theme().chart_1\n                        } else {\n                            cx.theme().muted_foreground.opacity(0.6)\n                        }\n                    })\n                    .tick_margin(5),\n                false,\n                cx,\n            )\n        )\n}\n```\n\n## Customization Options\n\n### Color Schemes\n\n```rust\n// Theme-based colors (recommended)\nLineChart::new(data)\n    .x(|d| d.x.clone())\n    .y(|d| d.y)\n    .stroke(cx.theme().chart_1)\n\n// Custom color palette\nlet colors = [\n    cx.theme().success,\n    cx.theme().warning,\n    cx.theme().destructive,\n    cx.theme().info,\n    cx.theme().chart_1,\n];\n\nBarChart::new(data)\n    .x(|d| d.category.clone())\n    .y(|d| d.value)\n    .fill(|d| colors[d.category_index % colors.len()])\n```\n\n### Responsive Design\n\n```rust\n// Container with responsive sizing\ndiv()\n    .flex_1()\n    .min_h(px(300.))\n    .max_h(px(600.))\n    .w_full()\n    .child(\n        LineChart::new(data)\n            .x(|d| d.x.clone())\n            .y(|d| d.y)\n    )\n```\n\n### Grid and Axis Styling\n\nCharts automatically include:\n\n- Grid lines with dashed appearance\n- X-axis labels with smart positioning\n- Y-axis scaling starting from zero\n- Responsive tick spacing based on `tick_margin`\n\n## Performance Considerations\n\n### Large Datasets\n\n```rust\n// For large datasets, consider data sampling\nlet sampled_data: Vec<_> = data\n    .iter()\n    .step_by(5) // Show every 5th point\n    .cloned()\n    .collect();\n\nLineChart::new(sampled_data)\n    .x(|d| d.date.clone())\n    .y(|d| d.value)\n    .tick_margin(3) // Reduce tick density\n```\n\n### Memory Optimization\n\n```rust\n// Use efficient data accessors\nLineChart::new(data)\n    .x(|d| d.date.clone()) // Clone only when necessary\n    .y(|d| d.value)        // Direct field access\n```\n\n## Integration Examples\n\n### With State Management\n\n```rust\nstruct ChartComponent {\n    data: Vec<DataPoint>,\n    chart_type: ChartType,\n    time_range: TimeRange,\n}\n\nimpl ChartComponent {\n    fn render_chart(&self, cx: &mut Context<Self>) -> impl IntoElement {\n        match self.chart_type {\n            ChartType::Line => LineChart::new(self.filtered_data())\n                .x(|d| d.date.clone())\n                .y(|d| d.value)\n                .into_any_element(),\n            ChartType::Bar => BarChart::new(self.filtered_data())\n                .x(|d| d.date.clone())\n                .y(|d| d.value)\n                .into_any_element(),\n            ChartType::Area => AreaChart::new(self.filtered_data())\n                .x(|d| d.date.clone())\n                .y(|d| d.value)\n                .into_any_element(),\n        }\n    }\n\n    fn filtered_data(&self) -> Vec<DataPoint> {\n        self.data\n            .iter()\n            .filter(|d| self.time_range.contains(&d.date))\n            .cloned()\n            .collect()\n    }\n}\n```\n\n### Real-time Updates\n\n```rust\nstruct LiveChart {\n    data: Vec<DataPoint>,\n    max_points: usize,\n}\n\nimpl LiveChart {\n    fn add_data_point(&mut self, point: DataPoint) {\n        self.data.push(point);\n        if self.data.len() > self.max_points {\n            self.data.remove(0); // Remove oldest point\n        }\n    }\n\n    fn render(&self, cx: &mut Context<Self>) -> impl IntoElement {\n        LineChart::new(self.data.clone())\n            .x(|d| d.timestamp.clone())\n            .y(|d| d.value)\n            .linear()\n            .dot()\n    }\n}\n```\n\n[LineChart]: https://docs.rs/gpui-component/latest/gpui_component/chart/struct.LineChart.html\n[BarChart]: https://docs.rs/gpui-component/latest/gpui_component/chart/struct.BarChart.html\n[AreaChart]: https://docs.rs/gpui-component/latest/gpui_component/chart/struct.AreaChart.html\n[PieChart]: https://docs.rs/gpui-component/latest/gpui_component/chart/struct.PieChart.html\n[CandlestickChart]: https://docs.rs/gpui-component/latest/gpui_component/chart/struct.CandlestickChart.html\n"
  },
  {
    "path": "docs/docs/components/checkbox.md",
    "content": "---\ntitle: Checkbox\ndescription: A control that allows the user to toggle between checked and not checked.\n---\n\n# Checkbox\n\nA checkbox component for binary choices. Supports labels, disabled state, and different sizes.\n\n## Import\n\n```rust\nuse gpui_component::checkbox::Checkbox;\n```\n\n## Usage\n\n### Basic Checkbox\n\n```rust\nCheckbox::new(\"my-checkbox\")\n    .label(\"Accept terms and conditions\")\n    .checked(false)\n    .on_click(|checked, _, _| {\n        println!(\"Checkbox is now: {}\", checked);\n    })\n```\n\nThe `on_click` callback is triggered when the user toggles the checkbox, receiving the **new checked state**.\n\n### Controlled Checkbox\n\n```rust\nstruct MyView {\n    is_checked: bool,\n}\n\nimpl Render for MyView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        Checkbox::new(\"checkbox\")\n            .label(\"Option\")\n            .checked(self.is_checked)\n            .on_click(cx.listener(|view, checked, _, cx| {\n                view.is_checked = *checked;\n                cx.notify();\n            }))\n    }\n}\n```\n\n### Different Sizes\n\n```rust\nCheckbox::new(\"cb\").text_xs().label(\"Extra Small\")\nCheckbox::new(\"cb\").text_sm().label(\"Small\")\nCheckbox::new(\"cb\").label(\"Medium\") // default\nCheckbox::new(\"cb\").text_lg().label(\"Large\")\n```\n\n### Disabled State\n\n```rust\nCheckbox::new(\"checkbox\")\n    .label(\"Disabled checkbox\")\n    .disabled(true)\n    .checked(false)\n```\n\n### Without Label\n\n```rust\nCheckbox::new(\"checkbox\")\n    .checked(true)\n```\n\n### Custom Tab Order\n\n```rust\nCheckbox::new(\"checkbox\")\n    .label(\"Custom tab order\")\n    .tab_index(2)\n    .tab_stop(true)\n```\n\n## API Reference\n\n- [Checkbox]\n\n### Styling\n\nImplements `Sizable` and `Disableable` traits:\n\n- `text_xs()` - Extra small text\n- `text_sm()` - Small text\n- `text_base()` - Base text (default)\n- `text_lg()` - Large text\n- `disabled(bool)` - Disabled state\n\n## Examples\n\n### Checkbox List\n\n```rust\nv_flex()\n    .gap_2()\n    .child(Checkbox::new(\"cb1\").label(\"Option 1\").checked(true))\n    .child(Checkbox::new(\"cb2\").label(\"Option 2\").checked(false))\n    .child(Checkbox::new(\"cb3\").label(\"Option 3\").checked(false))\n```\n\n### Form Integration\n\n```rust\nstruct FormView {\n    agree_terms: bool,\n    subscribe: bool,\n}\n\nv_flex()\n    .gap_3()\n    .child(\n        Checkbox::new(\"terms\")\n            .label(\"I agree to the terms and conditions\")\n            .checked(self.agree_terms)\n            .on_click(cx.listener(|view, checked, _, cx| {\n                view.agree_terms = *checked;\n                cx.notify();\n            }))\n    )\n    .child(\n        Checkbox::new(\"subscribe\")\n            .label(\"Subscribe to newsletter\")\n            .checked(self.subscribe)\n            .on_click(cx.listener(|view, checked, _, cx| {\n                view.subscribe = *checked;\n                cx.notify();\n            }))\n    )\n```\n\n[Checkbox]: https://docs.rs/gpui-component/latest/gpui_component/checkbox/struct.Checkbox.html\n"
  },
  {
    "path": "docs/docs/components/clipboard.md",
    "content": "---\ntitle: Clipboard\ndescription: A button component that helps you copy text or other content to your clipboard.\n---\n\n# Clipboard\n\nThe Clipboard component provides an easy way to copy text or other data to the user's clipboard. It renders as a button with a copy icon that changes to a checkmark when content is successfully copied. The component supports both static values and dynamic content through callback functions.\n\n## Import\n\n```rust\nuse gpui_component::clipboard::Clipboard;\n```\n\n## Usage\n\n### Basic Clipboard\n\n```rust\nClipboard::new(\"my-clipboard\")\n    .value(\"Text to copy\")\n    .on_copied(|value, window, cx| {\n        window.push_notification(format!(\"Copied: {}\", value), cx)\n    })\n```\n\n### Using Dynamic Values\n\nThe `value_fn` method allows you to provide a closure that generates the content to be copied at the time of the copy action.\n\n- This is useful when the content to be copied depends on the current state of the application.\n- And in some cases, it may have a larger overhead to compute, so you only want to do it when the user actually clicks the copy button.\n\n```rust\nlet state = some_state.clone();\nClipboard::new(\"dynamic-clipboard\")\n    .value_fn(move |_, cx| {\n        state.read(cx).get_current_value()\n    })\n    .on_copied(|value, window, cx| {\n        window.push_notification(format!(\"Copied: {}\", value), cx)\n    })\n```\n\n### With Custom Content\n\n```rust\nuse gpui_component::label::Label;\n\n h_flex()\n     .gap_2()\n     .child(Label::new(\"Share URL\"))\n     .child(Icon::new(IconName::Share))\n     .child(\n        Clipboard::new(\"custom-clipboard\")\n        .value(\"https://example.com\")\n     )\n```\n\n### In Input Fields\n\nThe Clipboard component is commonly used as a suffix in input fields:\n\n```rust\nuse gpui_component::input::{InputState, Input};\n\nlet url_state = cx.new(|cx| InputState::new(window, cx).default_value(\"https://github.com\"));\n\nInput::new(&url_state)\n    .suffix(\n        Clipboard::new(\"url-clipboard\")\n            .value_fn({\n                let state = url_state.clone();\n                move |_, cx| state.read(cx).value()\n            })\n            .on_copied(|value, window, cx| {\n                window.push_notification(format!(\"URL copied: {}\", value), cx)\n            })\n    )\n```\n\n## API Reference\n\n- [Clipboard]\n\n## Examples\n\n### Simple Text Copy\n\n```rust\nClipboard::new(\"simple\")\n    .value(\"Hello, World!\")\n```\n\n### With User Feedback\n\n```rust\nh_flex()\n    .gap_2()\n    .child(Label::new(\"Your API Key:\"))\n    .child(\n        Clipboard::new(\"feedback\")\n            .value(\"sk-1234567890abcdef\")\n            .on_copied(|_, window, cx| {\n                window.push_notification(\"API key copied to clipboard\", cx)\n            })\n    )\n```\n\n### Form Field Integration\n\n```rust\nuse gpui_component::{\n    input::{InputState, Input},\n    h_flex, label::Label\n};\n\nlet api_key = \"sk-1234567890abcdef\";\n\nh_flex()\n    .gap_2()\n    .items_center()\n    .child(Label::new(\"API Key:\"))\n    .child(\n        Input::new(&input_state)\n            .value(api_key)\n            .readonly(true)\n            .suffix(\n                Clipboard::new(\"api-key-copy\")\n                    .value(api_key)\n                    .on_copied(|_, window, cx| {\n                        window.push_notification(\"API key copied!\", cx)\n                    })\n            )\n    )\n```\n\n### Dynamic Content Copy\n\n```rust\nstruct AppState {\n    current_url: String,\n}\n\nlet app_state = cx.new(|_| AppState {\n    current_url: \"https://example.com\".to_string()\n});\n\nClipboard::new(\"current-url\")\n    .value_fn({\n        let state = app_state.clone();\n        move |_, cx| {\n            SharedString::from(state.read(cx).current_url.clone())\n        }\n    })\n    .on_copied(|url, window, cx| {\n        window.push_notification(format!(\"Shared: {}\", url), cx)\n    })\n```\n\n## Data Types\n\nThe Clipboard component currently supports copying text strings to the clipboard. It uses GPUI's `ClipboardItem::new_string()` method, which handles:\n\n- Plain text strings\n- UTF-8 encoded content\n- Cross-platform clipboard integration\n\n[Clipboard]: https://docs.rs/gpui-component/latest/gpui_component/clipboard/struct.Clipboard.html\n"
  },
  {
    "path": "docs/docs/components/collapsible.md",
    "content": "---\ntitle: Collapsible\ndescription: An interactive element which expands/collapses.\n---\n\n# Collapsible\n\nAn interactive element which expands/collapses.\n\n## Import\n\n```rust\nuse gpui_component::collapsible::Collapsible;\n```\n\n## Usage\n\n### Basic Use\n\n```rust\nCollapsible::new()\n    .max_w_128()\n    .gap_1()\n    .open(self.open)\n    .child(\n        \"This is a collapsible component. \\\n        Click the header to expand or collapse the content.\",\n    )\n    .content(\n        \"This is the full content of the Collapsible component. \\\n        It is only visible when the component is expanded. \\n\\\n        You can put any content you like here, including text, images, \\\n        or other UI elements.\",\n    )\n    .child(\n        h_flex().justify_center().child(\n            Button::new(\"toggle1\")\n                .icon(IconName::ChevronDown)\n                .label(\"Show more\")\n                .when(open, |this| {\n                    this.icon(IconName::ChevronUp).label(\"Show less\")\n                })\n                .xsmall()\n                .link()\n                .on_click({\n                    cx.listener(move |this, _, _, cx| {\n                        this.open = !this.open;\n                        cx.notify();\n                    })\n                }),\n        ),\n    )\n```\n\nWe can use `open` method to control the collapsed state. If false, the `content` method added child elements will be hidden.\n\n[Collapsible]: https://docs.rs/gpui-component/latest/gpui_component/collapsible/struct.Collapsible.html\n"
  },
  {
    "path": "docs/docs/components/color-picker.md",
    "content": "---\ntitle: ColorPicker\ndescription: A comprehensive color selection interface with support for multiple color formats, presets, and alpha channel.\n---\n\n# ColorPicker\n\nA versatile color picker component that provides an intuitive interface for color selection. Features include color palettes, hex input, featured colors, and support for various color formats including RGB, HSL, and hex values with alpha channel support.\n\n## Import\n\n```rust\nuse gpui_component::color_picker::{ColorPicker, ColorPickerState, ColorPickerEvent};\n```\n\n## Usage\n\n### Basic Color Picker\n\n```rust\nuse gpui::{Entity, Window, Context};\n\n// Create color picker state\nlet color_picker = cx.new(|cx|\n    ColorPickerState::new(window, cx)\n        .default_value(cx.theme().primary)\n);\n\n// Create the color picker component\nColorPicker::new(&color_picker)\n```\n\n### With Event Handling\n\n```rust\nuse gpui::{Subscription, Entity};\n\nlet color_picker = cx.new(|cx| ColorPickerState::new(window, cx));\n\nlet _subscription = cx.subscribe(&color_picker, |this, _, ev, _| match ev {\n    ColorPickerEvent::Change(color) => {\n        if let Some(color) = color {\n            println!(\"Selected color: {}\", color.to_hex());\n            // Handle color change\n        }\n    }\n});\n\nColorPicker::new(&color_picker)\n```\n\n### Setting Default Color\n\n```rust\nuse gpui::Hsla;\n\nlet color_picker = cx.new(|cx|\n    ColorPickerState::new(window, cx)\n        .default_value(cx.theme().blue) // Set default color\n);\n```\n\n### Different Sizes\n\n```rust\n// Small color picker\nColorPicker::new(&color_picker).small()\n\n// Medium color picker (default)\nColorPicker::new(&color_picker)\n\n// Large color picker\nColorPicker::new(&color_picker).large()\n\n// Extra small color picker\nColorPicker::new(&color_picker).xsmall()\n```\n\n### With Custom Featured Colors\n\n```rust\nuse gpui::Hsla;\n\nlet featured_colors = vec![\n    cx.theme().red,\n    cx.theme().green,\n    cx.theme().blue,\n    cx.theme().yellow,\n    // Add your custom colors\n];\n\nColorPicker::new(&color_picker)\n    .featured_colors(featured_colors)\n```\n\n### With Icon Instead of Color Square\n\n```rust\nuse gpui_component::IconName;\n\nColorPicker::new(&color_picker)\n    .icon(IconName::Palette)\n```\n\n### With Label\n\n```rust\nColorPicker::new(&color_picker)\n    .label(\"Background Color\")\n```\n\n### Custom Anchor Position\n\n```rust\nuse gpui::Corner;\n\nColorPicker::new(&color_picker)\n    .anchor(Corner::TopRight) // Dropdown opens to top-right\n```\n\n## Color Selection Interface\n\n### Color Palettes\n\nThe color picker includes predefined color palettes organized by color family:\n\n- **Stone**: Neutral grays and stone colors\n- **Red**: Red color variations from light to dark\n- **Orange**: Orange color variations\n- **Yellow**: Yellow color variations\n- **Green**: Green color variations\n- **Cyan**: Cyan color variations\n- **Blue**: Blue color variations\n- **Purple**: Purple color variations\n- **Pink**: Pink color variations\n\nEach palette provides multiple shades and tints of the base color, allowing for precise color selection.\n\n### Featured Colors Section\n\nA customizable section at the top of the picker that displays frequently used or brand colors. If not specified, defaults to theme colors:\n\n- Primary colors from the current theme\n- Light variants of theme colors\n- Essential UI colors (red, blue, green, yellow, cyan, magenta)\n\n### Hex Input Field\n\nA text input field that allows direct entry of hex color values:\n\n- Supports standard 6-digit hex format (#RRGGBB)\n- Real-time validation and preview\n- Updates color picker state automatically\n- Press Enter to confirm selection\n\n## Color Formats\n\n### RGB (Red, Green, Blue)\n\nColors are internally represented using GPUI's `Hsla` format but can be converted to RGB:\n\n```rust\nlet color = cx.theme().blue;\n// Access RGB components through Hsla methods\n```\n\n### HSL (Hue, Saturation, Lightness)\n\nNative format used by the color picker:\n\n```rust\nuse gpui::Hsla;\n\n// Create HSL color\nlet color = Hsla::hsl(240.0, 100.0, 50.0); // Blue color\n\n// Access components\nlet hue = color.h;\nlet saturation = color.s;\nlet lightness = color.l;\n```\n\n### Hex Format\n\nStandard web hex format with # prefix:\n\n```rust\n// Convert color to hex\nlet hex_string = color.to_hex(); // Returns \"#3366FF\"\n\n// Parse hex string to color\nif let Ok(color) = Hsla::parse_hex(\"#3366FF\") {\n    // Use parsed color\n}\n```\n\n## Alpha Channel\n\nFull alpha channel support for transparency:\n\n```rust\nuse gpui::hsla;\n\n// Create color with alpha\nlet semi_transparent = hsla(0.5, 0.8, 0.6, 0.7); // 70% opacity\n\n// Modify existing color opacity\nlet transparent_blue = cx.theme().blue.opacity(0.5);\n```\n\nThe color picker preserves alpha values when selecting colors and allows modification through the alpha component of HSLA colors.\n\n## API Reference\n\n- [ColorPicker]\n- [ColorPickerState]\n- [ColorPickerEvent]\n\n## Examples\n\n### Color Theme Editor\n\n```rust\nstruct ThemeEditor {\n    primary_color: Entity<ColorPickerState>,\n    secondary_color: Entity<ColorPickerState>,\n    accent_color: Entity<ColorPickerState>,\n}\n\nimpl ThemeEditor {\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let primary_color = cx.new(|cx|\n            ColorPickerState::new(window, cx)\n                .default_value(cx.theme().primary)\n        );\n\n        let secondary_color = cx.new(|cx|\n            ColorPickerState::new(window, cx)\n                .default_value(cx.theme().secondary)\n        );\n\n        let accent_color = cx.new(|cx|\n            ColorPickerState::new(window, cx)\n                .default_value(cx.theme().accent)\n        );\n\n        Self {\n            primary_color,\n            secondary_color,\n            accent_color,\n        }\n    }\n\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_4()\n            .child(\n                h_flex()\n                    .gap_2()\n                    .items_center()\n                    .child(\"Primary Color:\")\n                    .child(ColorPicker::new(&self.primary_color))\n            )\n            .child(\n                h_flex()\n                    .gap_2()\n                    .items_center()\n                    .child(\"Secondary Color:\")\n                    .child(ColorPicker::new(&self.secondary_color))\n            )\n            .child(\n                h_flex()\n                    .gap_2()\n                    .items_center()\n                    .child(\"Accent Color:\")\n                    .child(ColorPicker::new(&self.accent_color))\n            )\n    }\n}\n```\n\n### Brand Color Selector\n\n```rust\nuse gpui_component::{Sizable as _};\n\nlet brand_colors = vec![\n    Hsla::parse_hex(\"#FF6B6B\").unwrap(), // Brand Red\n    Hsla::parse_hex(\"#4ECDC4\").unwrap(), // Brand Teal\n    Hsla::parse_hex(\"#45B7D1\").unwrap(), // Brand Blue\n    Hsla::parse_hex(\"#96CEB4\").unwrap(), // Brand Green\n    Hsla::parse_hex(\"#FFEAA7\").unwrap(), // Brand Yellow\n];\n\nColorPicker::new(&color_picker)\n    .featured_colors(brand_colors)\n    .label(\"Brand Color\")\n    .large()\n```\n\n### Toolbar Color Picker\n\n```rust\nuse gpui_component::{Sizable as _, IconName);\n\nColorPicker::new(&text_color_picker)\n    .icon(IconName::Type)\n    .small()\n    .anchor(Corner::BottomLeft)\n```\n\n### Color Palette Builder\n\n```rust\nstruct ColorPalette {\n    colors: Vec<Entity<ColorPickerState>>,\n}\n\nimpl ColorPalette {\n    fn add_color(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        let color_picker = cx.new(|cx| ColorPickerState::new(window, cx));\n\n        // Subscribe to color changes\n        cx.subscribe(&color_picker, |this, _, ev, _| match ev {\n            ColorPickerEvent::Change(color) => {\n                if let Some(color) = color {\n                    this.update_palette_preview();\n                }\n            }\n        });\n\n        self.colors.push(color_picker);\n        cx.notify();\n    }\n\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        h_flex()\n            .gap_2()\n            .children(\n                self.colors.iter().map(|color_picker| {\n                    ColorPicker::new(color_picker).small()\n                })\n            )\n            .child(\n                Button::new(\"add-color\")\n                    .icon(IconName::Plus)\n                    .ghost()\n                    .on_click(cx.listener(|this, _, window, cx| {\n                        this.add_color(window, cx);\n                    }))\n            )\n    }\n}\n```\n\n### With Color Validation\n\n```rust\nlet color_picker = cx.new(|cx| ColorPickerState::new(window, cx));\n\nlet _subscription = cx.subscribe(&color_picker, |this, _, ev, _| match ev {\n    ColorPickerEvent::Change(color) => {\n        if let Some(color) = color {\n            // Validate color accessibility\n            if this.validate_contrast(color) {\n                this.apply_color(color);\n            } else {\n                this.show_contrast_warning();\n            }\n        }\n    }\n});\n```\n\n[ColorPicker]: https://docs.rs/gpui-component/latest/gpui_component/color_picker/struct.ColorPicker.html\n[ColorPickerState]: https://docs.rs/gpui-component/latest/gpui_component/color_picker/struct.ColorPickerState.html\n[ColorPickerEvent]: https://docs.rs/gpui-component/latest/gpui_component/color_picker/enum.ColorPickerEvent.html\n"
  },
  {
    "path": "docs/docs/components/data-table.md",
    "content": "---\ntitle: DataTable\ndescription: High-performance data table with virtual scrolling, sorting, filtering, and column management.\n---\n\n# Data Table\n\nA comprehensive data table component designed for handling large datasets with high performance. Features virtual scrolling, column configuration, sorting, filtering, row/column/cell selection, and custom cell rendering. Perfect for displaying tabular data with thousands of rows while maintaining smooth performance.\n\n## Key Features\n\n- **Multiple Selection Modes**: Row, column, and individual cell selection\n- **Virtual Scrolling**: Handle thousands of rows with smooth performance\n- **Column Management**: Resizable, movable, and fixed columns\n- **Sorting**: Built-in column sorting support\n- **Keyboard Navigation**: Full keyboard support for all selection modes\n- **Custom Cell Rendering**: Render any content in table cells\n- **Context Menus**: Right-click support for rows and cells\n- **Infinite Loading**: Load more data as user scrolls\n- **Events**: Comprehensive event system for user interactions\n\n## Import\n\n```rust\nuse gpui_component::table::{\n    DataTable, TableState, TableDelegate,\n    Column, ColumnSort, ColumnFixed,\n    TableEvent\n};\n```\n\n## Usage\n\n### Basic Table\n\nTo create a table, you need to implement the `TableDelegate` trait and provide column definitions, and use `TableState` to manage the table state.\n\n```rust\nuse std::ops::Range;\nuse gpui::{App, Context, Window, IntoElement};\nuse gpui_component::table::{DataTable, TableDelegate, Column, ColumnSort};\n\nstruct MyData {\n    id: usize,\n    name: String,\n    age: u32,\n    email: String,\n}\n\nstruct MyTableDelegate {\n    data: Vec<MyData>,\n    columns: Vec<Column>,\n}\n\nimpl MyTableDelegate {\n    fn new() -> Self {\n        Self {\n            data: vec![\n                MyData { id: 1, name: \"John\".to_string(), age: 30, email: \"john@example.com\".to_string() },\n                MyData { id: 2, name: \"Jane\".to_string(), age: 25, email: \"jane@example.com\".to_string() },\n            ],\n            columns: vec![\n                Column::new(\"id\", \"ID\").width(60.),\n                Column::new(\"name\", \"Name\").width(150.).sortable(),\n                Column::new(\"age\", \"Age\").width(80.).sortable(),\n                Column::new(\"email\", \"Email\").width(200.),\n            ],\n        }\n    }\n}\n\nimpl TableDelegate for MyTableDelegate {\n    fn columns_count(&self, _: &App) -> usize {\n        self.columns.len()\n    }\n\n    fn rows_count(&self, _: &App) -> usize {\n        self.data.len()\n    }\n\n    fn column(&self, col_ix: usize, _: &App) -> &Column {\n        &self.columns[col_ix]\n    }\n\n    fn render_td(&mut self, row_ix: usize, col_ix: usize, _: &mut Window, _: &mut Context<TableState<Self>>) -> impl IntoElement {\n        let row = &self.data[row_ix];\n        let col = &self.columns[col_ix];\n\n        match col.key.as_ref() {\n            \"id\" => row.id.to_string(),\n            \"name\" => row.name.clone(),\n            \"age\" => row.age.to_string(),\n            \"email\" => row.email.clone(),\n            _ => \"\".to_string(),\n        }\n    }\n}\n\n// Create the table\nlet delegate = MyTableDelegate::new();\nlet state = cx.new(|cx| TableState::new(delegate, window, cx));\n```\n\n### Column Configuration\n\nColumns provide extensive configuration options:\n\n```rust\n// Basic column\nColumn::new(\"id\", \"ID\")\n\n// Sortable column\nColumn::new(\"name\", \"Name\")\n    .sortable()\n    .width(150.)\n\n// Right-aligned column\nColumn::new(\"price\", \"Price\")\n    .text_right()\n    .sortable()\n\n// Fixed column (pinned to left)\nColumn::new(\"actions\", \"Actions\")\n    .fixed(ColumnFixed::Left)\n    .resizable(false)\n    .movable(false)\n\n// Column with custom padding\nColumn::new(\"description\", \"Description\")\n    .width(200.)\n    .paddings(px(8.))\n\n// Non-resizable column\nColumn::new(\"status\", \"Status\")\n    .width(100.)\n    .resizable(false)\n\n// Custom sort orders\nColumn::new(\"created\", \"Created\")\n    .ascending() // Default ascending\n// or\nColumn::new(\"modified\", \"Modified\")\n    .descending() // Default descending\n```\n\n### Virtual Scrolling for Large Datasets\n\nThe table automatically handles virtual scrolling for optimal performance:\n\n```rust\nstruct LargeDataDelegate {\n    data: Vec<Record>, // Could be 10,000+ items\n    columns: Vec<Column>,\n}\n\nimpl TableDelegate for LargeDataDelegate {\n    fn rows_count(&self, _: &App) -> usize {\n        self.data.len() // No performance impact regardless of size\n    }\n\n    // Only visible rows are rendered\n    fn render_td(&mut self, row_ix: usize, col_ix: usize, _: &mut Window, _: &mut Context<TableState<Self>>) -> impl IntoElement {\n        // This is only called for visible rows\n        // Efficiently render cell content\n        let row = &self.data[row_ix];\n        format_cell_data(row, col_ix)\n    }\n\n    // Track visible range for optimizations\n    fn visible_rows_changed(&mut self, visible_range: Range<usize>, _: &mut Window, _: &mut Context<TableState<Self>>) {\n        // Only update data for visible rows if needed\n        // This is called when user scrolls\n    }\n}\n```\n\n### Sorting Implementation\n\nImplement sorting in your delegate:\n\n```rust\nimpl TableDelegate for MyTableDelegate {\n    fn perform_sort(&mut self, col_ix: usize, sort: ColumnSort, _: &mut Window, _: &mut Context<TableState<Self>>) {\n        let col = &self.columns[col_ix];\n\n        match col.key.as_ref() {\n            \"name\" => {\n                match sort {\n                    ColumnSort::Ascending => self.data.sort_by(|a, b| a.name.cmp(&b.name)),\n                    ColumnSort::Descending => self.data.sort_by(|a, b| b.name.cmp(&a.name)),\n                    ColumnSort::Default => {\n                        // Reset to original order or default sort\n                        self.data.sort_by(|a, b| a.id.cmp(&b.id));\n                    }\n                }\n            }\n            \"age\" => {\n                match sort {\n                    ColumnSort::Ascending => self.data.sort_by(|a, b| a.age.cmp(&b.age)),\n                    ColumnSort::Descending => self.data.sort_by(|a, b| b.age.cmp(&a.age)),\n                    ColumnSort::Default => self.data.sort_by(|a, b| a.id.cmp(&b.id)),\n                }\n            }\n            _ => {}\n        }\n    }\n}\n```\n\n### ContextMenu\n\n```rust\nimpl TableDelegate for MyTableDelegate {\n    // Context menu for right-click\n    fn context_menu(&mut self, row_ix: usize, menu: PopupMenu, _: &mut Window, _: &mut Context<TableState<Self>>) -> PopupMenu {\n        let row = &self.data[row_ix];\n        menu.menu(format!(\"Edit {}\", row.name), Box::new(EditRowAction(row_ix)))\n            .menu(\"Delete\", Box::new(DeleteRowAction(row_ix)))\n            .separator()\n            .menu(\"Duplicate\", Box::new(DuplicateRowAction(row_ix)))\n    }\n}\n```\n\n### Cell Rendering\n\nCreate rich cell content with custom rendering:\n\n```rust\nimpl TableDelegate for MyTableDelegate {\n    fn render_td(&mut self, row_ix: usize, col_ix: usize, _: &mut Window, cx: &mut Context<TableState<Self>>) -> impl IntoElement {\n        let row = &self.data[row_ix];\n        let col = &self.columns[col_ix];\n\n        match col.key.as_ref() {\n            \"status\" => {\n                // Custom status badge\n                let (color, text) = match row.status {\n                    Status::Active => (cx.theme().green, \"Active\"),\n                    Status::Inactive => (cx.theme().red, \"Inactive\"),\n                    Status::Pending => (cx.theme().yellow, \"Pending\"),\n                };\n\n                div()\n                    .px_2()\n                    .py_1()\n                    .rounded(px(4.))\n                    .bg(color.opacity(0.1))\n                    .text_color(color)\n                    .child(text)\n            }\n            \"progress\" => {\n                // Progress bar\n                div()\n                    .w_full()\n                    .h(px(8.))\n                    .bg(cx.theme().muted)\n                    .rounded(px(4.))\n                    .child(\n                        div()\n                            .h_full()\n                            .w(percentage(row.progress))\n                            .bg(cx.theme().primary)\n                            .rounded(px(4.))\n                    )\n            }\n            \"actions\" => {\n                // Action buttons\n                h_flex()\n                    .gap_1()\n                    .child(Button::new(format!(\"edit-{}\", row_ix)).text().icon(IconName::Edit))\n                    .child(Button::new(format!(\"delete-{}\", row_ix)).text().icon(IconName::Trash))\n            }\n            \"avatar\" => {\n                // User avatar with image\n                h_flex()\n                    .items_center()\n                    .gap_2()\n                    .child(\n                        div()\n                            .w(px(32.))\n                            .h(px(32.))\n                            .rounded_full()\n                            .bg(cx.theme().accent)\n                            .flex()\n                            .items_center()\n                            .justify_center()\n                            .child(row.name.chars().next().unwrap_or('?').to_string())\n                    )\n                    .child(row.name.clone())\n            }\n            _ => row.get_field_value(col.key.as_ref()).into_any_element(),\n        }\n    }\n}\n```\n\n### Selection Modes\n\nThe table supports three distinct selection modes:\n\n```rust\n// Row selection mode (default)\nlet state = cx.new(|cx| {\n    TableState::new(delegate, window, cx)\n        .row_selectable(true)  // Enable row selection\n        .col_selectable(false)\n        .cell_selectable(false)\n});\n\n// Column selection mode\nlet state = cx.new(|cx| {\n    TableState::new(delegate, window, cx)\n        .row_selectable(false)\n        .col_selectable(true)  // Enable column selection\n        .cell_selectable(false)\n});\n\n// Cell selection mode\nlet state = cx.new(|cx| {\n    TableState::new(delegate, window, cx)\n        .row_selectable(true)   // Keep row selection for row selector column\n        .col_selectable(false)\n        .cell_selectable(true)  // Enable cell selection\n});\n```\n\n### Column Resizing and Moving\n\nEnable dynamic column management:\n\n```rust\n// Configure table features\nlet state = cx.new(|cx| {\n    TableState::new(delegate, window, cx)\n        .col_resizable(true)  // Allow column resizing\n        .col_movable(true)    // Allow column reordering\n        .sortable(true)       // Enable sorting\n        .col_selectable(true) // Allow column selection\n        .row_selectable(true) // Allow row selection\n});\n\n// Listen for column changes\ncx.subscribe_in(&state, window, |view, table, event, _, cx| {\n    match event {\n        TableEvent::ColumnWidthsChanged(widths) => {\n            // Save column widths to user preferences\n            save_column_widths(widths);\n        }\n        TableEvent::MoveColumn(from_ix, to_ix) => {\n            // Save column order\n            save_column_order(from_ix, to_ix);\n        }\n        _ => {}\n    }\n}).detach();\n```\n\n### Infinite Loading / Pagination\n\nImplement loading more data as user scrolls:\n\n```rust\nimpl TableDelegate for MyTableDelegate {\n    fn has_more(&self, _: &App) -> bool {\n        self.has_more_data\n    }\n\n    fn load_more_threshold(&self) -> usize {\n        50 // Load more when 50 rows from bottom\n    }\n\n    fn load_more(&mut self, _: &mut Window, cx: &mut Context<TableState<Self>>) {\n        if self.loading {\n            return; // Prevent multiple loads\n        }\n\n        self.loading = true;\n\n        // Spawn async task to load data\n        cx.spawn(async move |view, cx| {\n            let new_data = fetch_more_data().await;\n\n            cx.update(|cx| {\n                view.update(cx, |view, _| {\n                    let delegate = view.table.delegate_mut();\n                    delegate.data.extend(new_data);\n                    delegate.loading = false;\n                    delegate.has_more_data = !new_data.is_empty();\n                });\n            })\n        }).detach();\n    }\n\n    fn loading(&self, _: &App) -> bool {\n        self.loading\n    }\n}\n```\n\n### Table Styling\n\nCustomize table appearance:\n\n```rust\nlet state = cx.new(|cx| {\n    TableState::new(delegate, window, cx)\n});\n\n// In render\nDataTable::new(&state)\n    .stripe(true)           // Alternating row colors\n    .bordered(true)           // Border around table\n    .scrollbar_visible(true, true) // Vertical, horizontal scrollbars\n```\n\n## Examples\n\n### Financial Data Table\n\n```rust\nstruct StockData {\n    symbol: String,\n    price: f64,\n    change: f64,\n    change_percent: f64,\n    volume: u64,\n}\n\nimpl TableDelegate for StockTableDelegate {\n    fn render_td(&mut self, row_ix: usize, col_ix: usize, _: &mut Window, cx: &mut Context<TableState<Self>>) -> impl IntoElement {\n        let stock = &self.stocks[row_ix];\n        let col = &self.columns[col_ix];\n\n        match col.key.as_ref() {\n            \"symbol\" => div().font_weight(FontWeight::BOLD).child(stock.symbol.clone()),\n            \"price\" => div().text_right().child(format!(\"${:.2}\", stock.price)),\n            \"change\" => {\n                let color = if stock.change >= 0.0 { cx.theme().green } else { cx.theme().red };\n                div()\n                    .text_right()\n                    .text_color(color)\n                    .child(format!(\"{:+.2}\", stock.change))\n            }\n            \"change_percent\" => {\n                let color = if stock.change_percent >= 0.0 { cx.theme().green } else { cx.theme().red };\n                div()\n                    .text_right()\n                    .text_color(color)\n                    .child(format!(\"{:+.1}%\", stock.change_percent * 100.0))\n            }\n            \"volume\" => div().text_right().child(format!(\"{:,}\", stock.volume)),\n            _ => div(),\n        }\n    }\n}\n```\n\n### User Management Table\n\n```rust\nstruct UserTableDelegate {\n    users: Vec<User>,\n    columns: Vec<Column>,\n}\n\nimpl UserTableDelegate {\n    fn new() -> Self {\n        Self {\n            users: Vec::new(),\n            columns: vec![\n                Column::new(\"avatar\", \"\").width(50.).resizable(false).movable(false),\n                Column::new(\"name\", \"Name\").width(150.).sortable().fixed_left(),\n                Column::new(\"email\", \"Email\").width(200.).sortable(),\n                Column::new(\"role\", \"Role\").width(100.).sortable(),\n                Column::new(\"status\", \"Status\").width(100.),\n                Column::new(\"last_login\", \"Last Login\").width(120.).sortable(),\n                Column::new(\"actions\", \"Actions\").width(100.).resizable(false),\n            ],\n        }\n    }\n}\n```\n\n### Cell Selection\n\nEnable individual cell selection for more granular control:\n\n```rust\nlet state = cx.new(|cx| {\n    TableState::new(delegate, window, cx)\n        .cell_selectable(true)  // Enable cell selection\n        .row_selectable(true)   // Also allow row selection\n});\n\n// Listen for cell events\ncx.subscribe_in(&state, window, |view, table, event, _, cx| {\n    match event {\n        TableEvent::SelectCell(row_ix, col_ix) => {\n            println!(\"Selected cell: ({}, {})\", row_ix, col_ix);\n        }\n        TableEvent::DoubleClickedCell(row_ix, col_ix) => {\n            // Open editor or detail view\n            open_cell_editor(row_ix, col_ix);\n        }\n        TableEvent::RightClickedCell(row_ix, col_ix) => {\n            // Show cell-specific context menu\n            show_cell_context_menu(row_ix, col_ix);\n        }\n        TableEvent::ClearSelection => {\n            println!(\"Selection cleared\");\n        }\n        _ => {}\n    }\n}).detach();\n```\n\n#### Cell Selection Features\n\nWhen cell selection is enabled:\n\n- **Click to select**: Click on any cell to select it\n- **Row selector column**: A dedicated column appears on the left for selecting entire rows\n- **Keyboard navigation**: Arrow keys navigate between cells (not rows/columns)\n- **Double-click support**: Trigger actions like editing by double-clicking cells\n- **Right-click support**: Show context menus specific to cell content\n- **Visual feedback**: Selected cells show highlight with border\n\n#### Programmatic Cell Selection\n\n```rust\n// Get the currently selected cell\nif let Some((row_ix, col_ix)) = state.read(cx).selected_cell() {\n    println!(\"Current cell: ({}, {})\", row_ix, col_ix);\n}\n\n// Select a specific cell programmatically\nstate.update(cx, |state, cx| {\n    state.set_selected_cell(5, 3, cx);  // Select row 5, column 3\n});\n\n// Clear all selections\nstate.update(cx, |state, cx| {\n    state.clear_selection(cx);\n});\n```\n\n#### Non-selectable Columns\n\nPrevent specific columns from being selected (useful for action columns):\n\n```rust\nColumn::new(\"actions\", \"Actions\")\n    .width(100.)\n    .selectable(false)  // This column's cells cannot be selected\n    .resizable(false)\n```\n\n#### Cell Selection with Custom Rendering\n\n```rust\nimpl TableDelegate for MyTableDelegate {\n    fn render_td(&mut self, row_ix: usize, col_ix: usize, _: &mut Window, cx: &mut Context<TableState<Self>>) -> impl IntoElement {\n        let row = &self.data[row_ix];\n        let col = &self.columns[col_ix];\n\n        // Render different content based on whether cell is selected\n        let is_selected = cx.entity().read(cx).selected_cell() == Some((row_ix, col_ix));\n\n        match col.key.as_ref() {\n            \"editable_field\" => {\n                if is_selected {\n                    // Show input when selected\n                    Input::new(format!(\"cell-{}-{}\", row_ix, col_ix))\n                        .value(row.field_value.clone())\n                        .into_any_element()\n                } else {\n                    // Show plain text when not selected\n                    div().child(row.field_value.clone()).into_any_element()\n                }\n            }\n            _ => div().child(row.get_value(col.key.as_ref())).into_any_element()\n        }\n    }\n}\n```\n\n## Keyboard Shortcuts\n\n### Row Selection Mode (default)\n\n- `↑/↓` - Navigate rows\n- `←/→` - Navigate columns\n- `Home` - Jump to first row/column\n- `End` - Jump to last row/column\n- `PageUp/PageDown` - Navigate by page\n- `Escape` - Clear selection\n\n### Cell Selection Mode\n\n- `↑/↓` - Navigate up/down within current column\n- `←/→` - Navigate left/right within current row\n- `Tab` - Move to next cell (right, then next row)\n- `Shift+Tab` - Move to previous cell\n- `Home` - Jump to first cell in current row\n- `End` - Jump to last cell in current row\n- `PageUp/PageDown` - Navigate by page within current column\n- `Escape` - Clear selection\n\n## API Reference\n\n### Core Types\n\n- [DataTable] - The data table component\n- [TableState] - Table state management\n- [TableDelegate] - Trait for implementing table data source\n- [Column] - Column configuration\n- [TableEvent] - Table events (selection, clicks, etc.)\n\n### Column Types\n\n- [ColumnSort] - Column sort direction enum\n- [ColumnFixed] - Column fixed position enum\n\n### Methods\n\n#### TableState\n\n- `new(delegate, window, cx)` - Create a new table state\n- `cell_selectable(bool)` - Enable/disable cell selection\n- `row_selectable(bool)` - Enable/disable row selection\n- `col_selectable(bool)` - Enable/disable column selection\n- `selected_cell()` - Get currently selected cell\n- `set_selected_cell(row_ix, col_ix, cx)` - Select a specific cell\n- `selected_row()` - Get currently selected row\n- `selected_col()` - Get currently selected column\n- `clear_selection(cx)` - Clear all selections\n- `scroll_to_row(row_ix, cx)` - Scroll to specific row\n- `scroll_to_col(col_ix, cx)` - Scroll to specific column\n\n#### Column\n\n- `new(key, name)` - Create a new column\n- `width(pixels)` - Set column width\n- `sortable()` - Make column sortable\n- `ascending()` - Set default sort to ascending\n- `descending()` - Set default sort to descending\n- `text_right()` - Right-align column text\n- `text_center()` - Center-align column text\n- `fixed(ColumnFixed)` - Pin column to left\n- `resizable(bool)` - Enable/disable column resizing\n- `movable(bool)` - Enable/disable column moving\n- `selectable(bool)` - Enable/disable column/cell selection\n- `paddings(edges)` - Set custom padding\n- `min_width(pixels)` - Set minimum width\n- `max_width(pixels)` - Set maximum width\n\n### Events\n\n- `SelectRow(usize)` - Row selected\n- `DoubleClickedRow(usize)` - Row double-clicked\n- `SelectColumn(usize)` - Column selected\n- `SelectCell(usize, usize)` - Cell selected (row_ix, col_ix)\n- `DoubleClickedCell(usize, usize)` - Cell double-clicked (row_ix, col_ix)\n- `RightClickedCell(usize, usize)` - Cell right-clicked (row_ix, col_ix)\n- `RightClickedRow(Option<usize>)` - Row right-clicked\n- `ColumnWidthsChanged(Vec<Pixels>)` - Column widths changed\n- `MoveColumn(usize, usize)` - Column moved (from_ix, to_ix)\n\n[DataTable]: https://docs.rs/gpui-component/latest/gpui_component/table/struct.DataTable.html\n[TableState]: https://docs.rs/gpui-component/latest/gpui_component/table/struct.TableState.html\n[TableDelegate]: https://docs.rs/gpui-component/latest/gpui_component/table/trait.TableDelegate.html\n[Column]: https://docs.rs/gpui-component/latest/gpui_component/table/struct.Column.html\n[TableEvent]: https://docs.rs/gpui-component/latest/gpui_component/table/enum.TableEvent.html\n[ColumnSort]: https://docs.rs/gpui-component/latest/gpui_component/table/enum.ColumnSort.html\n[ColumnFixed]: https://docs.rs/gpui-component/latest/gpui_component/table/enum.ColumnFixed.html\n"
  },
  {
    "path": "docs/docs/components/date-picker.md",
    "content": "---\ntitle: DatePicker\ndescription: A date picker component for selecting single dates or date ranges with calendar interface.\n---\n\n# DatePicker\n\nA flexible date picker component with calendar interface that supports single date selection, date range selection, custom date formatting, disabled dates, and preset ranges.\n\n## Import\n\n```rust\nuse gpui_component::{\n    date_picker::{DatePicker, DatePickerState, DateRangePreset, DatePickerEvent},\n    calendar::{Date, Matcher},\n};\n```\n\n## Usage\n\n### Basic Date Picker\n\n```rust\nlet date_picker = cx.new(|cx| DatePickerState::new(window, cx));\n\nDatePicker::new(&date_picker)\n```\n\n### With Initial Date\n\n```rust\nuse chrono::Local;\n\nlet date_picker = cx.new(|cx| {\n    let mut picker = DatePickerState::new(window, cx);\n    picker.set_date(Local::now().naive_local().date(), window, cx);\n    picker\n});\n\nDatePicker::new(&date_picker)\n```\n\n### Date Range Picker\n\n```rust\nuse chrono::{Local, Days};\n\n// Range mode picker\nlet range_picker = cx.new(|cx| DatePickerState::range(window, cx));\n\nDatePicker::new(&range_picker)\n    .number_of_months(2) // Show 2 months for easier range selection\n\n// With initial range\nlet range_picker = cx.new(|cx| {\n    let now = Local::now().naive_local().date();\n    let mut picker = DatePickerState::new(window, cx);\n    picker.set_date(\n        (now, now.checked_add_days(Days::new(7)).unwrap()),\n        window,\n        cx,\n    );\n    picker\n});\n\nDatePicker::new(&range_picker)\n    .number_of_months(2)\n```\n\n### With Custom Date Format\n\n```rust\nlet date_picker = cx.new(|cx| {\n    DatePickerState::new(window, cx)\n        .date_format(\"%Y-%m-%d\") // ISO format\n});\n\nDatePicker::new(&date_picker)\n\n// Other format examples:\n// \"%m/%d/%Y\" -> 12/25/2023\n// \"%B %d, %Y\" -> December 25, 2023\n// \"%d %b %Y\" -> 25 Dec 2023\n```\n\n### With Placeholder\n\n```rust\nDatePicker::new(&date_picker)\n    .placeholder(\"Select a date...\")\n```\n\n### Cleanable Date Picker\n\n```rust\nDatePicker::new(&date_picker)\n    .cleanable(true) // Show clear button when date is selected\n```\n\n### Different Sizes\n\n```rust\nDatePicker::new(&date_picker).large()\nDatePicker::new(&date_picker) // medium (default)\nDatePicker::new(&date_picker).small()\n```\n\n### Disabled State\n\n```rust\nDatePicker::new(&date_picker).disabled(true)\n```\n\n### Custom Appearance\n\n```rust\n// Without default styling\nDatePicker::new(&date_picker).appearance(false)\n\n// Use in custom container\ndiv()\n    .border_b_2()\n    .px_6()\n    .py_3()\n    .border_color(cx.theme().border)\n    .bg(cx.theme().secondary)\n    .child(DatePicker::new(&date_picker).appearance(false))\n```\n\n## Date Restrictions\n\n### Disabled Weekends\n\n```rust\nuse gpui_component::calendar;\n\nlet date_picker = cx.new(|cx| {\n    DatePickerState::new(window, cx)\n        .disabled_matcher(vec![0, 6]) // Sunday=0, Saturday=6\n});\n\nDatePicker::new(&date_picker)\n```\n\n### Disabled Date Range\n\n```rust\nuse chrono::{Local, Days};\n\nlet now = Local::now().naive_local().date();\n\nlet date_picker = cx.new(|cx| {\n    DatePickerState::new(window, cx)\n        .disabled_matcher(calendar::Matcher::range(\n            Some(now),\n            now.checked_add_days(Days::new(7)),\n        ))\n});\n\nDatePicker::new(&date_picker)\n```\n\n### Disabled Date Interval\n\n```rust\nlet date_picker = cx.new(|cx| {\n    DatePickerState::new(window, cx)\n        .disabled_matcher(calendar::Matcher::interval(\n            Some(now),\n            now.checked_add_days(Days::new(5))\n        ))\n});\n\nDatePicker::new(&date_picker)\n```\n\n### Custom Disabled Dates\n\n```rust\n// Disable first 5 days of each month\nlet date_picker = cx.new(|cx| {\n    DatePickerState::new(window, cx)\n        .disabled_matcher(calendar::Matcher::custom(|date| {\n            date.day0() < 5\n        }))\n});\n\nDatePicker::new(&date_picker)\n\n// Disable all Mondays\nlet date_picker = cx.new(|cx| {\n    DatePickerState::new(window, cx)\n        .disabled_matcher(calendar::Matcher::custom(|date| {\n            date.weekday() == chrono::Weekday::Mon\n        }))\n});\n```\n\n## Preset Ranges\n\n### Single Date Presets\n\n```rust\nuse chrono::{Utc, Duration};\n\nlet presets = vec![\n    DateRangePreset::single(\n        \"Yesterday\",\n        (Utc::now() - Duration::days(1)).naive_local().date(),\n    ),\n    DateRangePreset::single(\n        \"Last Week\",\n        (Utc::now() - Duration::weeks(1)).naive_local().date(),\n    ),\n    DateRangePreset::single(\n        \"Last Month\",\n        (Utc::now() - Duration::days(30)).naive_local().date(),\n    ),\n];\n\nDatePicker::new(&date_picker)\n    .presets(presets)\n```\n\n### Date Range Presets\n\n```rust\nlet range_presets = vec![\n    DateRangePreset::range(\n        \"Last 7 Days\",\n        (Utc::now() - Duration::days(7)).naive_local().date(),\n        Utc::now().naive_local().date(),\n    ),\n    DateRangePreset::range(\n        \"Last 30 Days\",\n        (Utc::now() - Duration::days(30)).naive_local().date(),\n        Utc::now().naive_local().date(),\n    ),\n    DateRangePreset::range(\n        \"Last 90 Days\",\n        (Utc::now() - Duration::days(90)).naive_local().date(),\n        Utc::now().naive_local().date(),\n    ),\n];\n\nDatePicker::new(&date_picker)\n    .number_of_months(2)\n    .presets(range_presets)\n```\n\n## Handle Date Selection Events\n\n```rust\nlet date_picker = cx.new(|cx| DatePickerState::new(window, cx));\n\ncx.subscribe(&date_picker, |view, _, event, _| {\n    match event {\n        DatePickerEvent::Change(date) => {\n            match date {\n                Date::Single(Some(selected_date)) => {\n                    println!(\"Single date selected: {}\", selected_date);\n                }\n                Date::Range(Some(start), Some(end)) => {\n                    println!(\"Date range selected: {} to {}\", start, end);\n                }\n                Date::Range(Some(start), None) => {\n                    println!(\"Range start selected: {}\", start);\n                }\n                _ => {\n                    println!(\"Date cleared\");\n                }\n            }\n        }\n    }\n});\n```\n\n## Multiple Months Display\n\n```rust\n// Show 2 months side by side (useful for date ranges)\nDatePicker::new(&date_picker)\n    .number_of_months(2)\n\n// Show 3 months\nDatePicker::new(&date_picker)\n    .number_of_months(3)\n```\n\n## Advanced Examples\n\n### Business Days Only\n\n```rust\nuse chrono::Weekday;\n\nlet business_days_picker = cx.new(|cx| {\n    DatePickerState::new(window, cx)\n        .disabled_matcher(calendar::Matcher::custom(|date| {\n            matches!(date.weekday(), Weekday::Sat | Weekday::Sun)\n        }))\n});\n\nDatePicker::new(&business_days_picker)\n    .placeholder(\"Select business day\")\n```\n\n### Date Range with Max Duration\n\n```rust\nuse chrono::Days;\n\nlet max_30_days_picker = cx.new(|cx| DatePickerState::range(window, cx));\n\ncx.subscribe(&max_30_days_picker, |view, picker, event, _| {\n    match event {\n        DatePickerEvent::Change(Date::Range(Some(start), Some(end))) => {\n            let duration = end.signed_duration_since(*start).num_days();\n            if duration > 30 {\n                // Reset to start date only if range exceeds 30 days\n                picker.update(cx, |state, cx| {\n                    state.set_date(Date::Range(Some(*start), None), window, cx);\n                });\n            }\n        }\n        _ => {}\n    }\n});\n\nDatePicker::new(&max_30_days_picker)\n    .number_of_months(2)\n    .placeholder(\"Select up to 30 days\")\n```\n\n### Quarter Presets\n\n```rust\nuse chrono::{NaiveDate, Datelike};\n\nfn quarter_start(year: i32, quarter: u32) -> NaiveDate {\n    let month = (quarter - 1) * 3 + 1;\n    NaiveDate::from_ymd_opt(year, month, 1).unwrap()\n}\n\nfn quarter_end(year: i32, quarter: u32) -> NaiveDate {\n    let month = quarter * 3;\n    let start = NaiveDate::from_ymd_opt(year, month, 1).unwrap();\n    NaiveDate::from_ymd_opt(year, month, start.days_in_month()).unwrap()\n}\n\nlet year = Local::now().year();\nlet quarterly_presets = vec![\n    DateRangePreset::range(\"Q1\", quarter_start(year, 1), quarter_end(year, 1)),\n    DateRangePreset::range(\"Q2\", quarter_start(year, 2), quarter_end(year, 2)),\n    DateRangePreset::range(\"Q3\", quarter_start(year, 3), quarter_end(year, 3)),\n    DateRangePreset::range(\"Q4\", quarter_start(year, 4), quarter_end(year, 4)),\n];\n\nDatePicker::new(&date_picker)\n    .presets(quarterly_presets)\n```\n\n## Examples\n\n### Event Date Picker\n\n```rust\nlet event_date = cx.new(|cx| {\n    let mut picker = DatePickerState::new(window, cx)\n        .date_format(\"%B %d, %Y\")\n        .disabled_matcher(calendar::Matcher::custom(|date| {\n            // Disable past dates\n            *date < Local::now().naive_local().date()\n        }));\n    picker\n});\n\nDatePicker::new(&event_date)\n    .placeholder(\"Choose event date\")\n    .cleanable(true)\n```\n\n### Booking System Date Range\n\n```rust\nlet booking_range = cx.new(|cx| DatePickerState::range(window, cx));\n\nlet booking_presets = vec![\n    DateRangePreset::range(\"This Weekend\", /* weekend dates */),\n    DateRangePreset::range(\"Next Week\", /* next week dates */),\n    DateRangePreset::range(\"This Month\", /* this month dates */),\n];\n\nDatePicker::new(&booking_range)\n    .number_of_months(2)\n    .presets(booking_presets)\n    .placeholder(\"Select check-in and check-out dates\")\n```\n\n### Financial Period Selector\n\n```rust\nlet financial_period = cx.new(|cx| {\n    DatePickerState::range(window, cx)\n        .date_format(\"%Y-%m-%d\")\n});\n\nDatePicker::new(&financial_period)\n    .number_of_months(3)\n    .presets(quarterly_presets)\n    .placeholder(\"Select reporting period\")\n```\n"
  },
  {
    "path": "docs/docs/components/description-list.md",
    "content": "---\ntitle: DescriptionList\ndescription: Use to display details with a tidy layout for key-value pairs.\n---\n\n# DescriptionList\n\nA versatile component for displaying key-value pairs in a structured, organized layout. Supports both horizontal and vertical layouts, multiple columns, borders, and different sizes. Perfect for showing detailed information like metadata, specifications, or summary data.\n\n## Import\n\n```rust\nuse gpui_component::description_list::{DescriptionList, DescriptionItem, DescriptionText};\n```\n\n## Usage\n\n### Basic Description List\n\n```rust\nDescriptionList::new()\n    .item(\"Name\", \"GPUI Component\", 1)\n    .item(\"Version\", \"0.1.0\", 1)\n    .item(\"License\", \"Apache-2.0\", 1)\n```\n\n### Using DescriptionItem Builder\n\n```rust\nDescriptionList::new()\n    .children([\n        DescriptionItem::new(\"Name\").value(\"GPUI Component\"),\n        DescriptionItem::new(\"Description\").value(\"UI components for building desktop applications\"),\n        DescriptionItem::new(\"Version\").value(\"0.1.0\"),\n    ])\n```\n\n### Different Layouts\n\n```rust\n// Horizontal layout (default)\nDescriptionList::horizontal()\n    .item(\"Platform\", \"macOS, Windows, Linux\", 1)\n    .item(\"Repository\", \"https://github.com/longbridge/gpui-component\", 1)\n\n// Vertical layout\nDescriptionList::vertical()\n    .item(\"Name\", \"GPUI Component\", 1)\n    .item(\"Description\", \"A comprehensive UI component library\", 1)\n```\n\n### Multiple Columns with Spans\n\n```rust\nDescriptionList::new()\n    .columns(3)\n    .child(DescriptionItem::new(\"Name\").value(\"GPUI Component\").span(1))\n    .children([\n        DescriptionItem::new(\"Version\").value(\"0.1.0\").span(1),\n        DescriptionItem::new(\"License\").value(\"Apache-2.0\").span(1),\n        DescriptionItem::new(\"Description\")\n            .value(\"Full-featured UI components for desktop applications\")\n            .span(3), // Spans all 3 columns\n        DescriptionItem::new(\"Repository\")\n            .value(\"https://github.com/longbridge/gpui-component\")\n            .span(2), // Spans 2 columns\n    ])\n```\n\n### With Dividers\n\n```rust\nDescriptionList::new()\n    .item(\"Name\", \"GPUI Component\", 1)\n    .item(\"Version\", \"0.1.0\", 1)\n    .divider() // Add a visual separator\n    .item(\"Author\", \"Longbridge\", 1)\n    .item(\"License\", \"Apache-2.0\", 1)\n```\n\n### Different Sizes\n\n```rust\n// Large size\nDescriptionList::new()\n    .large()\n    .item(\"Title\", \"Large Description List\", 1)\n\n// Medium size (default)\nDescriptionList::new()\n    .item(\"Title\", \"Medium Description List\", 1)\n\n// Small size\nDescriptionList::new()\n    .small()\n    .item(\"Title\", \"Small Description List\", 1)\n```\n\n### Without Borders\n\n```rust\nDescriptionList::new()\n    .bordered(false) // Remove borders for a cleaner look\n    .item(\"Name\", \"GPUI Component\", 1)\n    .item(\"Type\", \"UI Library\", 1)\n```\n\n### Custom Label Width (Horizontal Layout)\n\n```rust\nuse gpui::px;\n\nDescriptionList::horizontal()\n    .label_width(px(200.0)) // Set custom label width\n    .item(\"Very Long Label Name\", \"Short Value\", 1)\n    .item(\"Short\", \"Very long value that needs more space\", 1)\n```\n\n### Rich Content with Custom Elements\n\n```rust\nuse gpui_component::text::markdown;\n\nDescriptionList::new()\n    .columns(2)\n    .children([\n        DescriptionItem::new(\"Name\").value(\"GPUI Component\"),\n        DescriptionItem::new(\"Description\").value(\n            markdown(\n                \"UI components for building **fantastic** desktop applications.\",\n            ).into_any_element()\n        ),\n    ])\n```\n\n### Complex Example with Mixed Content\n\n```rust\nDescriptionList::new()\n    .columns(3)\n    .label_width(px(150.0))\n    .children([\n        DescriptionItem::new(\"Project Name\").value(\"GPUI Component\").span(1),\n        DescriptionItem::new(\"Version\").value(\"0.1.0\").span(1),\n        DescriptionItem::new(\"Status\").value(\"Active\").span(1),\n\n        DescriptionItem::Divider, // Full-width divider\n\n        DescriptionItem::new(\"Description\").value(\n            \"A comprehensive UI component library for building desktop applications with GPUI\"\n        ).span(3),\n\n        DescriptionItem::new(\"Repository\").value(\n            \"https://github.com/longbridge/gpui-component\"\n        ).span(2),\n        DescriptionItem::new(\"License\").value(\"Apache-2.0\").span(1),\n\n        DescriptionItem::new(\"Platforms\").value(\"macOS, Windows, Linux\").span(2),\n        DescriptionItem::new(\"Language\").value(\"Rust\").span(1),\n    ])\n```\n\n## Examples\n\n### User Profile Information\n\n```rust\nDescriptionList::new()\n    .columns(2)\n    .bordered(true)\n    .children([\n        DescriptionItem::new(\"Full Name\").value(\"John Doe\"),\n        DescriptionItem::new(\"Email\").value(\"john@example.com\"),\n        DescriptionItem::new(\"Phone\").value(\"+1 (555) 123-4567\"),\n        DescriptionItem::new(\"Department\").value(\"Engineering\"),\n        DescriptionItem::Divider,\n        DescriptionItem::new(\"Bio\").value(\n            \"Senior software engineer with 10+ years of experience in Rust and system programming.\"\n        ).span(2),\n    ])\n```\n\n### System Information\n\n```rust\nDescriptionList::vertical()\n    .small()\n    .bordered(false)\n    .children([\n        DescriptionItem::new(\"Operating System\").value(\"macOS 14.0\"),\n        DescriptionItem::new(\"Architecture\").value(\"Apple Silicon (M2)\"),\n        DescriptionItem::new(\"Memory\").value(\"16 GB\"),\n        DescriptionItem::new(\"Storage\").value(\"512 GB SSD\"),\n        DescriptionItem::new(\"GPU\").value(\"Apple M2 10-core GPU\"),\n    ])\n```\n\n### Product Specifications\n\n```rust\nDescriptionList::new()\n    .columns(3)\n    .large()\n    .children([\n        DescriptionItem::new(\"Model\").value(\"MacBook Pro\").span(1),\n        DescriptionItem::new(\"Year\").value(\"2023\").span(1),\n        DescriptionItem::new(\"Screen Size\").value(\"14-inch\").span(1),\n\n        DescriptionItem::new(\"Processor\").value(\"Apple M2 Pro\").span(2),\n        DescriptionItem::new(\"Base Price\").value(\"$1,999\").span(1),\n\n        DescriptionItem::Divider,\n\n        DescriptionItem::new(\"Key Features\").value(\n            \"Liquid Retina XDR display, ProMotion technology, P3 wide color gamut\"\n        ).span(3),\n    ])\n```\n\n### Configuration Settings\n\n```rust\nDescriptionList::horizontal()\n    .label_width(px(180.0))\n    .bordered(false)\n    .children([\n        DescriptionItem::new(\"Theme\").value(\"Dark Mode\"),\n        DescriptionItem::new(\"Font Size\").value(\"14px\"),\n        DescriptionItem::new(\"Auto Save\").value(\"Enabled\"),\n        DescriptionItem::new(\"Backup Frequency\").value(\"Every 30 minutes\"),\n        DescriptionItem::new(\"Language\").value(\"English (US)\"),\n    ])\n```\n\n## Design Guidelines\n\n- Use horizontal layout for simple key-value pairs\n- Use vertical layout when values are lengthy or complex\n- Limit columns to 3-4 for optimal readability\n- Use dividers to group related information\n- Keep labels concise and descriptive\n- Use consistent spacing with the size prop\n- Consider removing borders for embedded contexts\n"
  },
  {
    "path": "docs/docs/components/dialog.md",
    "content": "---\ntitle: Dialog\ndescription: A dialog dialog for displaying content in a layer above the app.\n---\n\n# Dialog\n\nDialog component for creating dialogs, confirmations, and alerts. Supports overlay, keyboard shortcuts, and various customizations.\n\n## Import\n\n```rust\nuse gpui_component::dialog::DialogButtonProps;\nuse gpui_component::WindowExt;\n```\n\n## Usage\n\n### Setup application root view for display of dialogs\n\nYou need to set up your application's root view to render the dialog layer. This is typically done in your main application struct's render method.\n\nThe [Root::render_dialog_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_dialog_layer) function handles rendering any active dialogs on top of your app content.\n\n```rust\nuse gpui_component::TitleBar;\n\nstruct MyApp {\n    view: AnyView,\n}\n\nimpl Render for MyApp {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let dialog_layer = Root::render_dialog_layer(window, cx);\n\n        div()\n            .size_full()\n            .child(\n                v_flex()\n                    .size_full()\n                    .child(TitleBar::new())\n                    .child(div().flex_1().overflow_hidden().child(self.view.clone())),\n            )\n            // Render the dialog layer on top of the app content\n            .children(dialog_layer)\n    }\n}\n```\n\n### Basic Dialog\n\n```rust\nwindow.open_dialog(cx, |dialog, _, _| {\n    dialog\n        .title(\"Welcome\")\n        .child(\"This is a dialog dialog.\")\n})\n```\n\n### Form Dialog\n\n```rust\nlet input = cx.new(|cx| InputState::new(window, cx));\n\nwindow.open_dialog(cx, |dialog, _, _| {\n    dialog\n        .title(\"User Information\")\n        .child(\n            v_flex()\n                .gap_3()\n                .child(\"Please enter your details:\")\n                .child(Input::new(&input))\n        )\n        .footer(|_, _, _, _| {\n            vec![\n                Button::new(\"ok\")\n                    .primary()\n                    .label(\"Submit\")\n                    .on_click(|_, window, cx| {\n                        window.close_dialog(cx);\n                    }),\n                Button::new(\"cancel\")\n                    .label(\"Cancel\")\n                    .on_click(|_, window, cx| {\n                        window.close_dialog(cx);\n                    }),\n            ]\n        })\n})\n```\n\n### Dialog with Icon\n\n```rust\nwindow.open_dialog(cx, |dialog, _, cx| {\n    dialog\n        .child(\n            h_flex()\n                .gap_3()\n                .child(Icon::new(IconName::TriangleAlert)\n                    .size_6()\n                    .text_color(cx.theme().warning))\n                .child(\"This action cannot be undone.\")\n        )\n})\n```\n\n### Scrollable Dialog\n\n```rust\nuse gpui_component::text::markdown;\n\nwindow.open_dialog(cx, |dialog, window, cx| {\n    dialog\n        .h(px(450.))\n        .title(\"Long Content\")\n        .child(markdown(long_markdown_text))\n})\n```\n\n### Dialog Options\n\n```rust\nwindow.open_dialog(cx, |dialog, _, _| {\n    dialog\n        .title(\"Custom Dialog\")\n        .overlay(true)              // Show overlay (default: true)\n        .overlay_closable(true)     // Click overlay to close (default: true)\n        .keyboard(true)             // ESC to close (default: true)\n        .close_button(false)        // Show close button (default: true)\n        .child(\"Dialog content\")\n})\n```\n\n### Nested Dialogs\n\n```rust\nwindow.open_dialog(cx, |dialog, _, _| {\n    dialog\n        .title(\"First Dialog\")\n        .child(\"This is the first dialog\")\n        .footer(|_, _, _, _| {\n            vec![\n                Button::new(\"open-another\")\n                    .label(\"Open Another Dialog\")\n                    .on_click(|_, window, cx| {\n                        window.open_dialog(cx, |dialog, _, _| {\n                            dialog\n                                .title(\"Second Dialog\")\n                                .child(\"This is nested\")\n                        });\n                    }),\n            ]\n        })\n})\n```\n\n### Custom Styling\n\n```rust\nwindow.open_dialog(cx, |dialog, _, cx| {\n    dialog\n        .rounded(cx.theme().radius_lg)\n        .bg(cx.theme().cyan)\n        .text_color(cx.theme().info_foreground)\n        .title(\"Custom Style\")\n        .child(\"Styled dialog content\")\n})\n```\n\n### Custom Padding\n\n```rust\nwindow.open_dialog(cx, |dialog, _, _| {\n    dialog\n        .p_3()                      // Custom padding\n        .title(\"Custom Padding\")\n        .child(\"Dialog with custom spacing\")\n})\n```\n\n### Close Dialog Programmatically\n\nThe `close_dialog` method can be used to close the active dialog from anywhere within the window context.\n\n```rust\n// Close top level active dialog.\nwindow.close_dialog(cx);\n\n// Close and perform action\nButton::new(\"submit\")\n    .primary()\n    .label(\"Submit\")\n    .on_click(|_, window, cx| {\n        // Do something\n        window.close_dialog(cx);\n    })\n```\n\n## Declarative API\n\nThe Dialog component now supports a declarative API that provides a more React-like component composition pattern using dedicated header, title, description, and footer components.\n\n### Import\n\n```rust\nuse gpui_component::dialog::{\n    Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter,\n};\n```\n\n### Trigger-based Dialog\n\nThe trigger-based approach allows you to create a dialog that opens when a trigger element is clicked. The dialog is defined inline with the trigger.\n\n```rust\nDialog::new(cx)\n    .trigger(\n        Button::new(\"open-dialog\")\n            .outline()\n            .label(\"Open Dialog\")\n    )\n    .content(|content, _, cx| {\n        content\n            .child(\n                DialogHeader::new()\n                    .child(DialogTitle::new().child(\"Account Created\"))\n                    .child(DialogDescription::new().child(\n                        \"Your account has been created successfully!\",\n                    ))\n            )\n            .child(\n                DialogFooter::new()\n                    .border_t_1()\n                    .border_color(cx.theme().border)\n                    .bg(cx.theme().muted)\n                    .child(\n                        Button::new(\"cancel\")\n                            .outline()\n                            .label(\"Cancel\")\n                            .on_click(|_, window, cx| {\n                                window.close_dialog(cx);\n                            })\n                    )\n                    .child(\n                        Button::new(\"ok\")\n                            .primary()\n                            .label(\"Save Changes\")\n                    )\n            )\n    })\n```\n\n### Content Builder Pattern\n\nUse the content builder pattern with `window.open_dialog` for more control over dialog creation:\n\n```rust\nwindow.open_dialog(cx, |dialog, _, _| {\n    dialog\n        .w(px(400.))\n        .content(|content, _, _| {\n            content\n                .child(\n                    DialogHeader::new()\n                        .child(DialogTitle::new().child(\"Custom Width\"))\n                        .child(DialogDescription::new().child(\n                            \"This dialog has a custom width of 400px.\",\n                        ))\n                )\n                .child(div().child(\n                    \"Content area with custom width configuration.\"\n                ))\n                .child(\n                    DialogFooter::new()\n                        .justify_center()\n                        .child(\n                            Button::new(\"cancel\")\n                                .flex_1()\n                                .outline()\n                                .label(\"Cancel\")\n                                .on_click(|_, window, cx| {\n                                    window.close_dialog(cx);\n                                })\n                        )\n                        .child(\n                            Button::new(\"done\")\n                                .flex_1()\n                                .primary()\n                                .label(\"Done\")\n                                .on_click(|_, window, cx| {\n                                    window.close_dialog(cx);\n                                })\n                        )\n                )\n        })\n})\n```\n\n### Declarative Components\n\n#### DialogHeader\n\nContainer for the dialog's title and description section.\n\n```rust\nDialogHeader::new()\n    .child(DialogTitle::new().child(\"Title\"))\n    .child(DialogDescription::new().child(\"Description\"))\n```\n\n#### DialogTitle\n\nDisplays the main title of the dialog with semantic styling.\n\n```rust\nDialogTitle::new()\n    .child(\"Account Settings\")\n```\n\n#### DialogDescription\n\nDisplays descriptive text below the title with muted styling.\n\n```rust\nDialogDescription::new()\n    .child(\"Update your account settings and preferences here.\")\n```\n\n#### DialogFooter\n\nContainer for action buttons and footer content. Automatically applies proper spacing and alignment.\n\n```rust\nDialogFooter::new()\n    .bg(cx.theme().muted)\n    .border_t_1()\n    .border_color(cx.theme().border)\n    .child(Button::new(\"cancel\").outline().label(\"Cancel\"))\n    .child(Button::new(\"save\").primary().label(\"Save\"))\n```\n\n### Form Dialog with Declarative API\n\n```rust\nlet name_input = cx.new(|cx| InputState::new(window, cx));\nlet email_input = cx.new(|cx| InputState::new(window, cx));\n\nDialog::new(cx)\n    .trigger(Button::new(\"edit-profile\").label(\"Edit Profile\"))\n    .content(|content, _, cx| {\n        content\n            .child(\n                DialogHeader::new()\n                    .child(DialogTitle::new().child(\"Edit Profile\"))\n                    .child(DialogDescription::new().child(\n                        \"Make changes to your profile here. Click save when done.\"\n                    ))\n            )\n            .child(\n                v_flex()\n                    .gap_4()\n                    .py_4()\n                    .child(\n                        v_flex()\n                            .gap_2()\n                            .child(\"Name\")\n                            .child(Input::new(&name_input).placeholder(\"Enter your name\"))\n                    )\n                    .child(\n                        v_flex()\n                            .gap_2()\n                            .child(\"Email\")\n                            .child(Input::new(&email_input).placeholder(\"Enter your email\"))\n                    )\n            )\n            .child(\n                DialogFooter::new()\n                    .child(Button::new(\"cancel\").outline().label(\"Cancel\"))\n                    .child(Button::new(\"save\").primary().label(\"Save Changes\"))\n            )\n    })\n```\n\n### Styled Footer\n\nCustomize the footer appearance with background colors, borders, and alignment:\n\n```rust\nDialogFooter::new()\n    .justify_center()        // Center align buttons\n    .bg(cx.theme().muted)    // Background color\n    .border_t_1()            // Top border\n    .border_color(cx.theme().border)\n    .child(Button::new(\"btn1\").flex_1().label(\"Cancel\"))\n    .child(Button::new(\"btn2\").flex_1().primary().label(\"Confirm\"))\n```\n\n### DialogContent Container\n\nThe `DialogContent` component provides a flexible container for dialog body content:\n\n```rust\nuse gpui_component::dialog::DialogContent;\n\nwindow.open_dialog(cx, |dialog, _, _| {\n    dialog.content(|content, _, cx| {\n        content\n            .child(DialogHeader::new()\n                .child(DialogTitle::new().child(\"Settings\"))\n                .child(DialogDescription::new().child(\"Configure your preferences\"))\n            )\n            .child(\n                div()\n                    .py_4()\n                    .child(\"Main content area\")\n            )\n            .child(DialogFooter::new()\n                .child(Button::new(\"close\").label(\"Close\"))\n            )\n    })\n})\n```\n\n## API Reference - Declarative Components\n\n### Dialog\n\n| Method                   | Description                                           |\n| ------------------------ | ----------------------------------------------------- |\n| `new(cx)`                | Create a new Dialog (no longer requires window param) |\n| `trigger(element)`       | Set trigger element that opens the dialog             |\n| `content(builder)`       | Set content using a builder function                  |\n| `w(px)` / `width(px)`    | Set dialog width                                      |\n| `max_w(px)`              | Set maximum width                                     |\n| `margin_top(px)`         | Set top margin                                        |\n| `overlay(bool)`          | Show/hide overlay (default: true)                     |\n| `overlay_closable(bool)` | Allow closing by clicking overlay (default: true)     |\n| `keyboard(bool)`         | Allow closing with ESC key (default: true)            |\n| `close_button(bool)`     | Show/hide close button (default: true)                |\n\n### DialogContent\n\nContainer for dialog body content. Automatically applies padding and flex layout.\n\n```rust\nDialogContent::new()\n    .child(DialogHeader::new()...)\n    .child(/* your content */)\n    .child(DialogFooter::new()...)\n```\n\n### DialogHeader\n\nContainer for title and description. Automatically applies vertical flex layout with proper gap.\n\n```rust\nDialogHeader::new()\n    .child(DialogTitle::new().child(\"Title\"))\n    .child(DialogDescription::new().child(\"Description\"))\n```\n\n### DialogTitle\n\nDisplays the dialog title with semantic styling (font-semibold, proper line-height).\n\n```rust\nDialogTitle::new()\n    .child(\"Dialog Title\")\n```\n\n### DialogDescription\n\nDisplays descriptive text with muted foreground color and proper text sizing.\n\n```rust\nDialogDescription::new()\n    .child(\"This is a description text that provides more context.\")\n```\n\n### DialogFooter\n\nContainer for footer buttons with automatic spacing and alignment.\n\n```rust\nDialogFooter::new()\n    .justify_end()  // Right align (default)\n    .child(Button::new(\"btn1\").label(\"Cancel\"))\n    .child(Button::new(\"btn2\").primary().label(\"OK\"))\n```\n\n## Breaking Changes\n\n### Dialog::new() Signature Change\n\nThe `Dialog::new()` constructor no longer requires a `window` parameter:\n\n```rust\n// Old API (deprecated)\nDialog::new(window, cx)\n\n// New API\nDialog::new(cx)\n```\n\n### Content Builder Function\n\nThe `.content()` method now accepts a builder function instead of a pre-built `DialogContent`:\n\n```rust\n// Old approach (still works)\ndialog.child(DialogHeader::new()...)\n\n// New declarative API\ndialog.content(|content, window, cx| {\n    content\n        .child(DialogHeader::new()...)\n        .child(DialogFooter::new()...)\n})\n```\n\n## Best Practices\n\n1. **Use Declarative Components**: Prefer `DialogHeader`, `DialogTitle`, `DialogDescription`, and `DialogFooter` for consistent styling\n2. **Trigger-based for Simple Cases**: Use the trigger pattern for straightforward dialogs that open from a button\n3. **Builder Pattern for Complex Dialogs**: Use `window.open_dialog` with content builder for dialogs requiring complex logic or state\n4. **Semantic Structure**: Always include `DialogHeader` with title and description for accessibility\n5. **Consistent Footer**: Use `DialogFooter` for all action buttons to maintain visual consistency\n6. **Proper Sizing**: Explicitly set dialog width when content requires specific dimensions\n"
  },
  {
    "path": "docs/docs/components/dropdown_button.md",
    "content": "---\ntitle: DropdownButton\ndescription: A DropdownButton is a combination of a button and a trigger button. It allows us to display a dropdown menu when the trigger is clicked, but the left Button can still respond to independent events.\n---\n\n# DropdownButton\n\nA [DropdownButton] is a combination of a button and a trigger button. It allows us to display a dropdown menu when the trigger is clicked, but the left Button can still respond to independent events.\n\nAnd more option methods of [Button] are also available for the DropdownButton, such as setting different variants using [ButtonCustomVariant], sizes using [Sizable], adding icons, loading states.\n\n## Import\n\n```rust\nuse gpui_component::button::{Button, DropdownButton};\n```\n\n## Usage\n\n```rust\nuse gpui::Corner;\n\nDropdownButton::new(\"dropdown\")\n    .button(Button::new(\"btn\").label(\"Click Me\"))\n    .dropdown_menu(|menu, _, _| {\n        menu.menu(\"Option 1\", Box::new(MyAction))\n            .menu(\"Option 2\", Box::new(MyAction))\n            .separator()\n            .menu(\"Option 3\", Box::new(MyAction))\n    })\n```\n\n### Variants\n\nSame as [Button], DropdownButton supports different variants.\n\n````rust\nDropdownButton::new(\"dropdown\")\n    .primary()\n    .button(Button::new(\"btn\").label(\"Primary\"))\n    .dropdown_menu(|menu, _, _| {\n        menu.menu(\"Option 1\", Box::new(MyAction))\n    })\n```\n\n### With custom anchor\n\n```rust\n// With custom anchor\nDropdownButton::new(\"dropdown\")\n    .button(Button::new(\"btn\").label(\"Click Me\"))\n    .dropdown_menu_with_anchor(Corner::BottomRight, |menu, _, _| {\n        menu.menu(\"Option 1\", Box::new(MyAction))\n    })\n````\n\n[Button]: https://docs.rs/gpui-component/latest/gpui_component/button/struct.Button.html\n[DropdownButton]: https://docs.rs/gpui-component/latest/gpui_component/button/struct.DropdownButton.html\n[ButtonCustomVariant]: https://docs.rs/gpui-component/latest/gpui_component/button/struct.ButtonCustomVariant.html\n[Sizable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Sizable.html\n"
  },
  {
    "path": "docs/docs/components/editor.md",
    "content": "---\ntitle: Editor\ndescription: Multi-line text input component with auto-resize, validation, and advanced editing features.\n---\n\n# Editor\n\nA powerful multi-line text input component that extends the basic input functionality with support for multiple lines, auto-resizing, syntax highlighting, line numbers, and code editing features. Perfect for forms, code editors, and content editing.\n\n## Import\n\n```rust\nuse gpui_component::input::{InputState, Input};\n```\n\n## Usage\n\n### Textarea\n\n```rust\nlet state = cx.new(|cx|\n    InputState::new(window, cx)\n        .multi_line(true)\n        .placeholder(\"Enter your message...\")\n);\n\nInput::new(&state)\n```\n\nWith fixed height Textarea:\n\n```rust\nlet state = cx.new(|cx|\n    InputState::new(window, cx)\n        .multi_line(true)\n        .rows(10) // Set number of rows\n        .placeholder(\"Enter text here...\")\n);\n\nInput::new(&state)\n    .h(px(320.)) // Set explicit height\n```\n\n### AutoGrow\n\n```rust\nlet state = cx.new(|cx|\n    InputState::new(window, cx)\n        .auto_grow(1, 5) // min_rows: 1, max_rows: 5\n        .placeholder(\"Type here and watch it grow...\")\n);\n\nInput::new(&state)\n```\n\n### CodeEditor\n\nGPUI Component's `InputState` supports a code editor mode with syntax highlighting, line numbers, and search functionality.\n\nIt design for high performance and can handle large files efficiently. We\nused [tree-sitter](https://tree-sitter.github.io/tree-sitter/) for syntax highlighting, and [ropey](https://github.com/cessen/ropey) for text storage and manipulation.\n\n```rust\nlet state = cx.new(|cx|\n    InputState::new(window, cx)\n        .code_editor(\"rust\") // Language for syntax highlighting\n        .line_number(true) // Show line numbers\n        .searchable(true) // Enable search functionality\n        .show_whitespaces(true) // Show whitespace characters\n        .default_value(\"fn main() {\\n    println!(\\\"Hello, world!\\\");\\n}\")\n);\n\nInput::new(&state)\n    .h_full() // Full height\n```\n\n#### Single Line Mode\n\nSometimes you may want to use the code editor features but restrict input to a single line, for example for code snippets or commands.\n\n```rust\nlet state = cx.new(|cx|\n    InputState::new(window, cx)\n        .code_editor(\"rust\")\n        .multi_line(false) // Single line\n        .default_value(\"println!(\\\"Hello, world!\\\");\")\n);\n\nInput::new(&state)\n```\n\n### TabSize\n\n```rust\nuse gpui_component::input::TabSize;\n\nlet state = cx.new(|cx|\n    InputState::new(window, cx)\n        .multi_line(true)\n        .tab_size(TabSize {\n            tab_size: 4,\n            hard_tabs: false, // Use spaces instead of tabs\n        })\n);\n\nInput::new(&state)\n```\n\n### Searchable\n\nThe search feature allows for all multi-line inputs to support searching through the content using `Ctrl+F` (or `Cmd+F` on Mac).\n\nIt provides a search bar with options to navigate between matches and highlight them.\n\nUse `searchable` method to enable:\n\n```rust\nlet state = cx.new(|cx|\n    InputState::new(window, cx)\n        .multi_line(true)\n        .searchable(true) // Enable Ctrl+F search\n        .rows(15)\n        .default_value(\"Search through this content...\")\n);\n\nInput::new(&state)\n```\n\n### SoftWrap\n\nBy default multi-line inputs have soft wrapping enabled, meaning long lines will wrap to fit the width of the textarea.\n\nYou can disable soft wrapping to allow horizontal scrolling instead:\n\n```rust\n// With soft wrap (default)\nlet state = cx.new(|cx|\n    InputState::new(window, cx)\n        .multi_line(true)\n        .soft_wrap(true)\n        .rows(6)\n);\n\n// Without soft wrap (horizontal scrolling)\nlet state = cx.new(|cx|\n    InputState::new(window, cx)\n        .multi_line(true)\n        .soft_wrap(false)\n        .rows(6)\n        .default_value(\"This is a very long line that will not wrap automatically but will show horizontal scrollbar instead.\")\n);\n```\n\n### Text Manipulation\n\n```rust\n// Insert text at cursor position\nstate.update(cx, |state, cx| {\n    state.insert(\"inserted text\", window, cx);\n});\n\n// Replace all content\nstate.update(cx, |state, cx| {\n    state.replace(\"new content\", window, cx);\n});\n\n// Set cursor position\nstate.update(cx, |state, cx| {\n    state.set_cursor_position(Position { line: 2, character: 5 }, window, cx);\n});\n\n// Get cursor position\nlet position = state.read(cx).cursor_position();\nprintln!(\"Line: {}, Column: {}\", position.line, position.character);\n```\n\n### Validation\n\n```rust\nlet state = cx.new(|cx|\n    InputState::new(window, cx)\n        .multi_line(true)\n        .validate(|text, _| {\n            // Validate that content is not empty and under 1000 chars\n            !text.trim().is_empty() && text.len() <= 1000\n        })\n);\n\nInput::new(&state)\n```\n\n### Handle Events\n\n```rust\ncx.subscribe_in(&state, window, |view, state, event, window, cx| {\n    match event {\n        InputEvent::Change => {\n            let content = state.read(cx).value();\n            println!(\"Content changed: {} characters\", content.len());\n        }\n        InputEvent::PressEnter { secondary } => {\n            if secondary {\n                println!(\"Shift+Enter pressed - insert line break\");\n            } else {\n                println!(\"Enter pressed - could submit form\");\n            }\n        }\n        InputEvent::Focus => println!(\"Textarea focused\"),\n        InputEvent::Blur => println!(\"Textarea blurred\"),\n    }\n});\n```\n\n### Disabled State\n\n```rust\nInput::new(&state)\n    .disabled(true)\n    .h(px(200.))\n```\n\n### Custom Styling\n\n```rust\n// Without default appearance\nInput::new(&state)\n    .appearance(false)\n    .h(px(200.))\n\n// Custom container styling\ndiv()\n    .bg(cx.theme().background)\n    .border_2()\n    .border_color(cx.theme().input)\n    .rounded(cx.theme().radius_lg)\n    .p_4()\n    .child(\n        Input::new(&state)\n            .appearance(false)\n            .h(px(150.))\n    )\n```\n\n## Examples\n\n### Comment Box\n\n```rust\nstruct CommentBox {\n    state: Entity<InputState>,\n    char_limit: usize,\n}\n\nimpl CommentBox {\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let state = cx.new(|cx|\n            InputState::new(window, cx)\n                .auto_grow(3, 8)\n                .placeholder(\"Write your comment...\")\n                .validate(|text, _| text.len() <= 500)\n        );\n\n        Self {\n            state,\n            char_limit: 500,\n        }\n    }\n}\n\nimpl Render for CommentBox {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let content = self.state.read(cx).value();\n        let char_count = content.len();\n        let remaining = self.char_limit.saturating_sub(char_count);\n\n        v_flex()\n            .gap_2()\n            .child(Input::new(&self.state))\n            .child(\n                h_flex()\n                    .justify_between()\n                    .child(\n                        div()\n                            .text_xs()\n                            .text_color(cx.theme().muted_foreground)\n                            .child(format!(\"{} characters remaining\", remaining))\n                    )\n                    .child(\n                        Button::new(\"submit\")\n                            .primary()\n                            .disabled(char_count == 0 || char_count > self.char_limit)\n                            .label(\"Post Comment\")\n                    )\n            )\n    }\n}\n```\n\n### Code Editor with Language Selection\n\n```rust\nstruct CodeEditor {\n    editor: Entity<InputState>,\n    language: String,\n}\n\nimpl CodeEditor {\n    fn set_language(&mut self, language: String, window: &mut Window, cx: &mut Context<Self>) {\n        self.language = language.clone();\n        self.editor.update(cx, |editor, cx| {\n            editor.set_highlighter(language, cx);\n        });\n    }\n}\n\nimpl Render for CodeEditor {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_3()\n            .child(\n                h_flex()\n                    .gap_2()\n                    .child(\"Language:\")\n                    .child(\n                        // Language selector dropdown would go here\n                        div().child(self.language.clone())\n                    )\n            )\n            .child(\n                Input::new(&self.editor)\n                    .h(px(400.))\n                    .bordered(true)\n            )\n    }\n}\n```\n\n### Text Editor with Toolbar\n\n```rust\nstruct TextEditor {\n    editor: Entity<InputState>,\n}\n\nimpl TextEditor {\n    fn format_bold(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        self.editor.update(cx, |editor, cx| {\n            if !editor.selected_range.is_empty() {\n                let selected = editor.selected_text().to_string();\n                editor.replace(&format!(\"**{}**\", selected), window, cx);\n            }\n        });\n    }\n}\n\nimpl Render for TextEditor {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_2()\n            .child(\n                h_flex()\n                    .gap_1()\n                    .p_2()\n                    .border_b_1()\n                    .border_color(cx.theme().border)\n                    .child(\n                        Button::new(\"bold\")\n                            .ghost()\n                            .icon(IconName::Bold)\n                            .on_click(cx.listener(Self::format_bold))\n                    )\n                    .child(\n                        Button::new(\"italic\")\n                            .ghost()\n                            .icon(IconName::Italic)\n                    )\n            )\n            .child(\n                Input::new(&self.editor)\n                    .h(px(300.))\n            )\n    }\n}\n```\n"
  },
  {
    "path": "docs/docs/components/focus-trap.md",
    "content": "---\ntitle: Focus Trap\ndescription: A utility element that traps keyboard focus within a container, preventing Tab navigation from escaping.\n---\n\n# Focus Trap\n\nFocus trap utility for constraining keyboard focus within a specific container. Essential for modal dialogs, sheets, and overlay components to provide proper keyboard navigation accessibility.\n\n**Note:** [Dialog](/docs/components/dialog) and [Sheet](/docs/components/sheet) components have focus trap built-in. You only need to manually use `focus_trap()` for custom modal-like components.\n\n## Import\n\n```rust\nuse gpui_component::FocusTrapElement;\n```\n\n## Usage\n\n### Basic Focus Trap\n\n```rust\nlet container_handle = cx.focus_handle();\n\nv_flex()\n    .child(Button::new(\"btn1\").label(\"Button 1\"))\n    .child(Button::new(\"btn2\").label(\"Button 2\"))\n    .child(Button::new(\"btn3\").label(\"Button 3\"))\n    .focus_trap(\"trap1\", &container_handle)\n// Pressing Tab will cycle: btn1 -> btn2 -> btn3 -> btn1\n// Focus will not escape to elements outside this container\n```\n\n### Multiple Focus Traps\n\nYou can have multiple independent focus trap areas in your application. Each trap operates independently:\n\n```rust\nlet trap1_handle = cx.focus_handle();\nlet trap2_handle = cx.focus_handle();\n\nv_flex()\n    .gap_4()\n    // First focus trap area\n    .child(\n        h_flex()\n            .gap_2()\n            .child(Button::new(\"trap1-1\").label(\"Area 1 - Button 1\"))\n            .child(Button::new(\"trap1-2\").label(\"Area 1 - Button 2\"))\n            .child(Button::new(\"trap1-3\").label(\"Area 1 - Button 3\"))\n            .focus_trap(\"trap1\", &trap1_handle)\n    )\n    // Second focus trap area\n    .child(\n        h_flex()\n            .gap_2()\n            .child(Button::new(\"trap2-1\").label(\"Area 2 - Button 1\"))\n            .child(Button::new(\"trap2-2\").label(\"Area 2 - Button 2\"))\n            .focus_trap(\"trap2\", &trap2_handle)\n    )\n```\n\n### Focus Trap with Dialog\n\n[Dialog] components have focus trap built-in automatically. You don't need to manually add `focus_trap()`:\n\n```rust\nwindow.open_dialog(cx, |dialog, _, _| {\n    dialog\n        .title(\"Settings\")\n        .child(\n            v_flex()\n                .gap_3()\n                .child(Button::new(\"save\").label(\"Save\"))\n                .child(Button::new(\"cancel\").label(\"Cancel\"))\n                .child(Button::new(\"reset\").label(\"Reset\"))\n        )\n    // Dialog internally uses focus_trap()\n    // Tab navigation automatically cycles: save -> cancel -> reset -> save\n})\n```\n\n### Focus Trap with Sheet\n\n[Sheet] components also have focus trap built-in automatically:\n\n```rust\nwindow.open_sheet(cx, |sheet, _, _| {\n    sheet\n        .title(\"Filter Options\")\n        .child(\n            v_flex()\n                .gap_2()\n                .child(Checkbox::new(\"option1\").label(\"Option 1\"))\n                .child(Checkbox::new(\"option2\").label(\"Option 2\"))\n                .child(Button::new(\"apply\").label(\"Apply Filters\"))\n        )\n    // Sheet internally uses focus_trap()\n    // Focus automatically cycles within the sheet panel\n})\n```\n\n## How It Works\n\nThe focus trap system consists of three key components:\n\n1. **FocusTrapContainer**: Wraps any container element and registers it as a focus trap area\n2. **FocusTrapManager**: Global state manager that tracks all active focus traps\n3. **Root Integration**: The [Root] view intercepts Tab/Shift-Tab events and enforces focus cycling\n\nWhen Tab or Shift-Tab is pressed:\n\n1. [Root] detects if the currently focused element is inside a focus trap\n2. If yes, it calculates the next focusable element within the same trap\n3. If focus would escape the trap, it cycles back to the beginning (Tab) or end (Shift-Tab)\n4. This prevents focus from leaving the trapped container\n\n### Built-in Focus Trap Components\n\nThe following components have focus trap functionality built-in and don't require manual `focus_trap()` calls:\n\n- **[Dialog]** - Modal dialogs automatically trap focus (see `dialog.rs:437`)\n- **[Sheet]** - Side panels automatically trap focus (see `sheet.rs:197`)\n\n## API Reference\n\n- [FocusTrapElement](https://docs.rs/gpui-component/latest/gpui_component/trait.FocusTrapElement.html)\n- [FocusTrapContainer](https://docs.rs/gpui-component/latest/gpui_component/struct.FocusTrapContainer.html)\n\n## Examples\n\n### Custom Modal with Focus Trap\n\n```rust\nstruct CustomModal {\n    container_handle: FocusHandle,\n}\n\nimpl CustomModal {\n    fn new(cx: &mut App) -> Self {\n        Self {\n            container_handle: cx.focus_handle(),\n        }\n    }\n}\n\nimpl Render for CustomModal {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .absolute()\n            .inset_0()\n            .flex()\n            .items_center()\n            .justify_center()\n            .child(\n                v_flex()\n                    .gap_4()\n                    .p_6()\n                    .bg(cx.theme().background)\n                    .rounded(cx.theme().radius_lg)\n                    .shadow_lg()\n                    .border_1()\n                    .border_color(cx.theme().border)\n                    .child(\"This is a modal dialog\")\n                    .child(\n                        h_flex()\n                            .gap_2()\n                            .child(Button::new(\"ok\").primary().label(\"OK\"))\n                            .child(Button::new(\"cancel\").label(\"Cancel\"))\n                    )\n                    .focus_trap(\"modal\", &self.container_handle)\n            )\n    }\n}\n```\n\n### Nested Focus Traps\n\nFocus traps support nesting. When multiple traps are active, the innermost trap takes precedence:\n\n```rust\nlet outer_handle = cx.focus_handle();\nlet inner_handle = cx.focus_handle();\n\ndiv()\n    .child(\n        v_flex()\n            .gap_4()\n            .p_4()\n            .border_1()\n            .border_color(cx.theme().border)\n            .child(Button::new(\"outer-1\").label(\"Outer Button 1\"))\n            .child(\n                // Inner trap takes precedence when focused\n                h_flex()\n                    .gap_2()\n                    .p_4()\n                    .bg(cx.theme().accent.opacity(0.1))\n                    .child(Button::new(\"inner-1\").label(\"Inner Button 1\"))\n                    .child(Button::new(\"inner-2\").label(\"Inner Button 2\"))\n                    .focus_trap(\"inner\", &inner_handle)\n            )\n            .child(Button::new(\"outer-2\").label(\"Outer Button 2\"))\n            .focus_trap(\"outer\", &outer_handle)\n    )\n```\n\n### Conditional Focus Trap\n\nYou can conditionally apply focus trapping based on application state:\n\n```rust\nstruct ModalView {\n    is_modal: bool,\n    container_handle: FocusHandle,\n}\n\nimpl Render for ModalView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let content = v_flex()\n            .gap_2()\n            .child(Button::new(\"btn1\").label(\"Button 1\"))\n            .child(Button::new(\"btn2\").label(\"Button 2\"))\n            .child(Button::new(\"btn3\").label(\"Button 3\"));\n\n        if self.is_modal {\n            // Apply focus trap when in modal mode\n            content.focus_trap(\"conditional\", &self.container_handle)\n                .into_any_element()\n        } else {\n            // Normal behavior without focus trap\n            content.into_any_element()\n        }\n    }\n}\n```\n\n## Accessibility Notes\n\n- Focus trapping is essential for modal dialogs and overlays to meet WCAG accessibility guidelines\n- Always provide a way to close or dismiss trapped focus areas (ESC key, close button)\n- The first focusable element in the trap should receive focus when the trap is activated\n- Use focus traps sparingly - only for truly modal interactions\n- Ensure keyboard navigation order is logical within the trapped area\n\n## See Also\n\n- [Root View System](/docs/root) - Manages focus trap behavior at the window level\n- [Dialog](/docs/components/dialog) - Uses focus trap automatically\n- [Sheet](/docs/components/sheet) - Uses focus trap automatically\n- [focus-trap-react](https://github.com/focus-trap/focus-trap-react) - Similar concept for React applications\n\n[Root]: https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html\n[FocusTrapElement]: https://docs.rs/gpui-component/latest/gpui_component/trait.FocusTrapElement.html\n[Dialog]: /docs/components/dialog\n[Sheet]: /docs/components/sheet\n"
  },
  {
    "path": "docs/docs/components/form.md",
    "content": "---\ntitle: Form\ndescription: Flexible form container with support for field layout, validation, and multi-column layouts.\n---\n\n# Form\n\nA comprehensive form component that provides structured layout for form fields with support for vertical/horizontal layouts, validation, field groups, and responsive multi-column layouts.\n\n## Import\n\n```rust\nuse gpui_component::form::{field, v_form, h_form, Form, Field};\n```\n\n## Usage\n\n### Basic Form\n\n```rust\nv_form()\n    .child(\n        field()\n            .label(\"Name\")\n            .child(Input::new(&name_input))\n    )\n    .child(\n        field()\n            .label(\"Email\")\n            .child(Input::new(&email_input))\n            .required(true)\n    )\n```\n\n### Horizontal Form Layout\n\n```rust\nh_form()\n    .label_width(px(120.))\n    .child(\n        field()\n            .label(\"First Name\")\n            .child(Input::new(&first_name))\n    )\n    .child(\n        field()\n            .label(\"Last Name\")\n            .child(Input::new(&last_name))\n    )\n```\n\n### Multi-Column Form\n\n```rust\nv_form()\n    .columns(2) // Two-column layout\n    .child(\n        field()\n            .label(\"First Name\")\n            .child(Input::new(&first_name))\n    )\n    .child(\n        field()\n            .label(\"Last Name\")\n            .child(Input::new(&last_name))\n    )\n    .child(\n        field()\n            .label(\"Bio\")\n            .col_span(2) // Span across both columns\n            .child(Input::new(&bio_input))\n    )\n```\n\n## Form Container and Layout\n\n### Vertical Layout (Default)\n\n```rust\nv_form()\n    .gap(px(12.))\n    .child(field().label(\"Name\").child(input))\n    .child(field().label(\"Email\").child(email_input))\n```\n\n### Horizontal Layout\n\n```rust\nh_form()\n    .label_width(px(100.))\n    .child(field().label(\"Name\").child(input))\n    .child(field().label(\"Email\").child(email_input))\n```\n\n### Custom Sizing\n\n```rust\nv_form()\n    .large() // Large form size\n    .label_text_size(rems(1.2))\n    .child(field().label(\"Title\").child(input))\n\nv_form()\n    .small() // Small form size\n    .child(field().label(\"Code\").child(input))\n```\n\n## Form Validation\n\n### Required Fields\n\n```rust\nfield()\n    .label(\"Email\")\n    .required(true) // Shows asterisk (*) next to label\n    .child(Input::new(&email_input))\n```\n\n### Field Descriptions\n\n```rust\nfield()\n    .label(\"Password\")\n    .description(\"Must be at least 8 characters long\")\n    .child(Input::new(&password_input))\n```\n\n### Dynamic Descriptions\n\n```rust\nfield()\n    .label(\"Bio\")\n    .description_fn(|_, _| {\n        div().child(\"Use at most 100 words to describe yourself.\")\n    })\n    .child(Input::new(&bio_input))\n```\n\n### Field Visibility\n\n```rust\nfield()\n    .label(\"Admin Settings\")\n    .visible(user.is_admin()) // Conditionally show field\n    .child(Switch::new(\"admin-mode\"))\n```\n\n## Submit Handling\n\n### Basic Submit Pattern\n\n```rust\nstruct FormView {\n    name_input: Entity<InputState>,\n    email_input: Entity<InputState>,\n}\n\nimpl FormView {\n    fn submit(&mut self, cx: &mut Context<Self>) {\n        let name = self.name_input.read(cx).value();\n        let email = self.email_input.read(cx).value();\n\n        // Validate inputs\n        if name.is_empty() || email.is_empty() {\n            // Show validation error\n            return;\n        }\n\n        // Submit form data\n        self.handle_submit(name, email, cx);\n    }\n}\n\n// Form with submit button\nv_form()\n    .child(field().label(\"Name\").child(Input::new(&self.name_input)))\n    .child(field().label(\"Email\").child(Input::new(&self.email_input)))\n    .child(\n        field()\n            .label_indent(false)\n            .child(\n                Button::new(\"submit\")\n                    .primary()\n                    .child(\"Submit\")\n                    .on_click(cx.listener(|this, _, _, cx| this.submit(cx)))\n            )\n    )\n```\n\n### Form with Action Buttons\n\n```rust\nv_form()\n    .child(field().label(\"Title\").child(Input::new(&title)))\n    .child(field().label(\"Content\").child(Input::new(&content)))\n    .child(\n        field()\n            .label_indent(false)\n            .child(\n                h_flex()\n                    .gap_2()\n                    .child(Button::new(\"save\").primary().child(\"Save\"))\n                    .child(Button::new(\"cancel\").child(\"Cancel\"))\n                    .child(Button::new(\"preview\").outline().child(\"Preview\"))\n            )\n    )\n```\n\n## Field Groups\n\n### Related Fields\n\n```rust\nv_form()\n    .child(\n        field()\n            .label(\"Name\")\n            .child(\n                h_flex()\n                    .gap_2()\n                    .child(div().flex_1().child(Input::new(&first_name)))\n                    .child(div().flex_1().child(Input::new(&last_name)))\n            )\n    )\n    .child(\n        field()\n            .label(\"Address\")\n            .items_start() // Align to start for multi-line content\n            .child(\n                v_flex()\n                    .gap_2()\n                    .child(Input::new(&street))\n                    .child(\n                        h_flex()\n                            .gap_2()\n                            .child(div().flex_1().child(Input::new(&city)))\n                            .child(div().w(px(100.)).child(Input::new(&zip)))\n                    )\n            )\n    )\n```\n\n### Custom Field Components\n\n```rust\nfield()\n    .label(\"Theme Color\")\n    .child(ColorPicker::new(&color_state).small())\n\nfield()\n    .label(\"Birth Date\")\n    .description(\"We'll send you a birthday gift!\")\n    .child(DatePicker::new(&date_state))\n\nfield()\n    .label(\"Notifications\")\n    .child(\n        v_flex()\n            .gap_2()\n            .child(Switch::new(\"email\").label(\"Email notifications\"))\n            .child(Switch::new(\"push\").label(\"Push notifications\"))\n            .child(Switch::new(\"sms\").label(\"SMS notifications\"))\n    )\n```\n\n### Conditional Fields\n\n```rust\nv_form()\n    .child(\n        field()\n            .label(\"Account Type\")\n            .child(Select::new(&account_type))\n    )\n    .child(\n        field()\n            .label(\"Company Name\")\n            .visible(is_business_account) // Show only for business accounts\n            .child(Input::new(&company_name))\n    )\n    .child(\n        field()\n            .label(\"Tax ID\")\n            .visible(is_business_account)\n            .required(is_business_account)\n            .child(Input::new(&tax_id))\n    )\n```\n\n## Grid Layout and Positioning\n\n### Column Spanning\n\n```rust\nv_form()\n    .columns(3) // Three-column grid\n    .child(field().label(\"First\").child(input1))\n    .child(field().label(\"Second\").child(input2))\n    .child(field().label(\"Third\").child(input3))\n    .child(\n        field()\n            .label(\"Full Width\")\n            .col_span(3) // Spans all three columns\n            .child(Input::new(&full_width))\n    )\n```\n\n### Column Positioning\n\n```rust\nv_form()\n    .columns(4)\n    .child(field().label(\"A\").child(input_a))\n    .child(field().label(\"B\").child(input_b))\n    .child(\n        field()\n            .label(\"Positioned\")\n            .col_start(1) // Start at column 1\n            .col_span(2)  // Span 2 columns\n            .child(input_positioned)\n    )\n```\n\n### Responsive Layout\n\n```rust\nv_form()\n    .columns(if is_mobile { 1 } else { 2 })\n    .child(field().label(\"Name\").child(name_input))\n    .child(field().label(\"Email\").child(email_input))\n    .child(\n        field()\n            .label(\"Bio\")\n            .when(!is_mobile, |field| field.col_span(2))\n            .child(bio_input)\n    )\n```\n\n## Examples\n\n### User Registration Form\n\n```rust\nstruct RegistrationForm {\n    first_name: Entity<InputState>,\n    last_name: Entity<InputState>,\n    email: Entity<InputState>,\n    password: Entity<InputState>,\n    confirm_password: Entity<InputState>,\n    terms_accepted: bool,\n}\n\nimpl Render for RegistrationForm {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_form()\n            .large()\n            .child(\n                field()\n                    .label(\"Personal Information\")\n                    .label_indent(false)\n                    .child(\n                        h_flex()\n                            .gap_3()\n                            .child(\n                                div().flex_1().child(\n                                    Input::new(&self.first_name)\n                                        .placeholder(\"First name\")\n                                )\n                            )\n                            .child(\n                                div().flex_1().child(\n                                    Input::new(&self.last_name)\n                                        .placeholder(\"Last name\")\n                                )\n                            )\n                    )\n            )\n            .child(\n                field()\n                    .label(\"Email\")\n                    .required(true)\n                    .child(Input::new(&self.email))\n            )\n            .child(\n                field()\n                    .label(\"Password\")\n                    .required(true)\n                    .description(\"Must be at least 8 characters\")\n                    .child(Input::new(&self.password))\n            )\n            .child(\n                field()\n                    .label(\"Confirm Password\")\n                    .required(true)\n                    .child(Input::new(&self.confirm_password))\n            )\n            .child(\n                field()\n                    .label_indent(false)\n                    .child(\n                        Checkbox::new(\"terms\")\n                            .label(\"I agree to the Terms of Service\")\n                            .checked(self.terms_accepted)\n                            .on_click(cx.listener(|this, checked, _, cx| {\n                                this.terms_accepted = *checked;\n                                cx.notify();\n                            }))\n                    )\n            )\n            .child(\n                field()\n                    .label_indent(false)\n                    .child(\n                        Button::new(\"register\")\n                            .primary()\n                            .large()\n                            .w_full()\n                            .child(\"Create Account\")\n                    )\n            )\n    }\n}\n```\n\n### Settings Form with Sections\n\n```rust\nv_form()\n    .column(2)\n    .child(\n        field()\n            .label(\"Profile\")\n            .label_indent(false)\n            .col_span(2)\n            .child(Divider::horizontal())\n    )\n    .child(\n        field()\n            .label(\"Display Name\")\n            .child(Input::new(&display_name))\n    )\n    .child(\n        field()\n            .label(\"Email\")\n            .child(Input::new(&email))\n    )\n    .child(\n        field()\n            .label(\"Bio\")\n            .col_span(2)\n            .items_start()\n            .child(Input::new(&bio))\n    )\n    .child(\n        field()\n            .label(\"Preferences\")\n            .label_indent(false)\n            .col_span(2)\n            .child(Divider::horizontal())\n    )\n    .child(\n        field()\n            .label(\"Theme\")\n            .child(Select::new(&theme_state))\n    )\n    .child(\n        field()\n            .label(\"Language\")\n            .child(Select::new(&language_state))\n    )\n    .child(\n        field()\n            .label_indent(false)\n            .child(Switch::new(\"notifications\").label(\"Enable notifications\"))\n    )\n    .child(\n        field()\n            .label_indent(false)\n            .child(Switch::new(\"marketing\").label(\"Marketing emails\"))\n    )\n```\n\n### Contact Form\n\n```rust\nv_form()\n    .child(\n        field()\n            .label(\"Contact Information\")\n            .child(\n                h_flex()\n                    .gap_2()\n                    .child(\n                        Select::new(&prefix_state)\n                            .w(px(80.))\n                    )\n                    .child(\n                        div().flex_1().child(\n                            Input::new(&name_input)\n                                .placeholder(\"Your name\")\n                        )\n                    )\n            )\n    )\n    .child(\n        field()\n            .label(\"Email\")\n            .required(true)\n            .child(Input::new(&email_input))\n    )\n    .child(\n        field()\n            .label(\"Subject\")\n            .child(Select::new(&subject_state))\n    )\n    .child(\n        field()\n            .label(\"Message\")\n            .required(true)\n            .items_start()\n            .description(\"Please describe your inquiry in detail\")\n            .child(Input::new(&message_input))\n    )\n    .child(\n        field()\n            .label_indent(false)\n            .child(\n                h_flex()\n                    .gap_2()\n                    .justify_between()\n                    .child(\n                        Checkbox::new(\"copy\")\n                            .label(\"Send me a copy\")\n                    )\n                    .child(\n                        h_flex()\n                            .gap_2()\n                            .child(Button::new(\"cancel\").child(\"Cancel\"))\n                            .child(Button::new(\"send\").primary().child(\"Send Message\"))\n                    )\n            )\n    )\n```\n"
  },
  {
    "path": "docs/docs/components/group-box.md",
    "content": "---\ntitle: GroupBox\ndescription: A styled container element with an optional title to group related content together.\n---\n\n# GroupBox\n\nThe GroupBox component is a versatile container that groups related content together with optional borders, backgrounds, and titles. It provides visual organization and semantic grouping for form controls, settings panels, and other related UI elements.\n\n## Import\n\n```rust\nuse gpui_component::group_box::{GroupBox, GroupBoxVariant, GroupBoxVariants as _};\n```\n\n## Usage\n\n### Basic GroupBox\n\n```rust\nGroupBox::new()\n    .child(\"Subscriptions\")\n    .child(Checkbox::new(\"all\").label(\"All\"))\n    .child(Checkbox::new(\"newsletter\").label(\"Newsletter\"))\n    .child(Button::new(\"save\").primary().label(\"Save\"))\n```\n\n### GroupBox Variants\n\n```rust\n// Normal variant (default) - no background or border\nGroupBox::new()\n    .child(\"Content without visual container\")\n\n// Fill variant - with background color\nGroupBox::new()\n    .fill()\n    .title(\"Settings\")\n    .child(\"Content with background\")\n\n// Outline variant - with border, no background\nGroupBox::new()\n    .outline()\n    .title(\"Preferences\")\n    .child(\"Content with border\")\n```\n\n### With Title\n\n```rust\nGroupBox::new()\n    .fill()\n    .title(\"Account Settings\")\n    .child(\n        h_flex()\n            .justify_between()\n            .child(\"Make profile private\")\n            .child(Switch::new(\"privacy\").checked(false))\n    )\n    .child(Button::new(\"save\").primary().label(\"Save Changes\"))\n```\n\n### Custom ID\n\n```rust\nGroupBox::new()\n    .id(\"user-preferences\")\n    .outline()\n    .title(\"User Preferences\")\n    .child(\"Preference controls...\")\n```\n\n### Custom Title Styling\n\n```rust\nuse gpui::{StyleRefinement, relative};\n\nGroupBox::new()\n    .outline()\n    .title(\"Custom Title\")\n    .title_style(\n        StyleRefinement::default()\n            .font_semibold()\n            .line_height(relative(1.0))\n            .px_3()\n            .text_color(cx.theme().accent)\n    )\n    .child(\"Content with custom title styling\")\n```\n\n### Custom Content Styling\n\n```rust\nGroupBox::new()\n    .fill()\n    .title(\"Custom Content Area\")\n    .content_style(\n        StyleRefinement::default()\n            .rounded_xl()\n            .py_3()\n            .px_4()\n            .border_2()\n            .border_color(cx.theme().accent)\n    )\n    .child(\"Content with custom styling\")\n```\n\n### Complex Example\n\n```rust\nGroupBox::new()\n    .id(\"notification-settings\")\n    .outline()\n    .bg(cx.theme().group_box)\n    .rounded_xl()\n    .p_5()\n    .title(\"Notification Preferences\")\n    .title_style(\n        StyleRefinement::default()\n            .font_semibold()\n            .line_height(relative(1.0))\n            .px_3()\n    )\n    .content_style(\n        StyleRefinement::default()\n            .rounded_xl()\n            .py_3()\n            .px_4()\n            .border_2()\n    )\n    .child(\n        v_flex()\n            .gap_3()\n            .child(\n                h_flex()\n                    .justify_between()\n                    .child(\"Email notifications\")\n                    .child(Switch::new(\"email\").checked(true))\n            )\n            .child(\n                h_flex()\n                    .justify_between()\n                    .child(\"Push notifications\")\n                    .child(Switch::new(\"push\").checked(false))\n            )\n            .child(\n                h_flex()\n                    .justify_between()\n                    .child(\"SMS notifications\")\n                    .child(Switch::new(\"sms\").checked(false))\n            )\n    )\n    .child(\n        h_flex()\n            .justify_end()\n            .gap_2()\n            .child(Button::new(\"cancel\").label(\"Cancel\"))\n            .child(Button::new(\"save\").primary().label(\"Save Settings\"))\n    )\n```\n\n## Examples\n\n### Form Section\n\n```rust\nGroupBox::new()\n    .fill()\n    .title(\"Personal Information\")\n    .child(\n        v_flex()\n            .gap_4()\n            .child(\n                h_flex()\n                    .gap_2()\n                    .child(Input::new(\"first-name\").placeholder(\"First Name\"))\n                    .child(Input::new(\"last-name\").placeholder(\"Last Name\"))\n            )\n            .child(Input::new(\"email\").placeholder(\"Email Address\"))\n            .child(\n                h_flex()\n                    .justify_end()\n                    .child(Button::new(\"update\").primary().label(\"Update Profile\"))\n            )\n    )\n```\n\n### Settings Panel\n\n```rust\nGroupBox::new()\n    .outline()\n    .title(\"Display Settings\")\n    .child(\n        v_flex()\n            .gap_3()\n            .child(\n                h_flex()\n                    .justify_between()\n                    .child(Label::new(\"Theme\"))\n                    .child(\n                        RadioGroup::horizontal(\"theme\")\n                            .child(Radio::new(\"light\").label(\"Light\"))\n                            .child(Radio::new(\"dark\").label(\"Dark\"))\n                            .child(Radio::new(\"auto\").label(\"Auto\"))\n                    )\n            )\n            .child(\n                h_flex()\n                    .justify_between()\n                    .child(Label::new(\"Font Size\"))\n                    .child(\n                        Select::new(\"font-size\")\n                            .option(\"small\", \"Small\")\n                            .option(\"medium\", \"Medium\")\n                            .option(\"large\", \"Large\")\n                    )\n            )\n    )\n```\n\n### Subscription Management\n\n```rust\nGroupBox::new()\n    .title(\"Email Subscriptions\")\n    .child(\n        v_flex()\n            .gap_2()\n            .child(Checkbox::new(\"newsletter\").label(\"Weekly Newsletter\"))\n            .child(Checkbox::new(\"updates\").label(\"Product Updates\"))\n            .child(Checkbox::new(\"security\").label(\"Security Alerts\"))\n            .child(Checkbox::new(\"marketing\").label(\"Marketing Communications\"))\n    )\n    .child(\n        h_flex()\n            .justify_between()\n            .mt_4()\n            .child(Button::new(\"unsubscribe-all\").link().label(\"Unsubscribe All\"))\n            .child(Button::new(\"save\").primary().label(\"Update Preferences\"))\n    )\n```\n\n### Without Title\n\n```rust\nGroupBox::new()\n    .outline()\n    .child(\n        h_flex()\n            .justify_between()\n            .items_center()\n            .child(\"Enable two-factor authentication\")\n            .child(Switch::new(\"2fa\").checked(false))\n    )\n```\n\n## Styling\n\nThe GroupBox component supports extensive customization through both built-in variants and custom styling:\n\n### Theme Integration\n\n```rust\n// Using theme colors\nGroupBox::new()\n    .fill()\n    .bg(cx.theme().group_box)\n    .title(\"Themed Group Box\")\n```\n\n### Custom Appearance\n\n```rust\nGroupBox::new()\n    .outline()\n    .border_2()\n    .border_color(cx.theme().accent)\n    .rounded(cx.theme().radius_lg)\n    .title(\"Custom Styled Group Box\")\n    .title_style(\n        StyleRefinement::default()\n            .text_color(cx.theme().accent)\n            .font_bold()\n    )\n```\n\n## Best Practices\n\n1. **Use titles for clarity** - Always include a descriptive title when grouping form controls\n2. **Choose appropriate variants** - Use `fill()` for primary content groups, `outline()` for secondary groupings\n3. **Maintain visual hierarchy** - Use GroupBox to create clear sections without overwhelming the interface\n4. **Group related content** - Only group logically related controls and information\n5. **Consider spacing** - The component automatically handles internal spacing, but consider external margins\n6. **Responsive design** - GroupBox adapts well to different screen sizes and container widths\n\n## Related Components\n\n- **Form**: Use GroupBox within forms to organize sections\n- **Dialog**: GroupBox works well within dialogs for organizing content\n- **Accordion**: For collapsible grouped content, consider using Accordion instead\n- **Card**: For elevated content containers with more visual weight\n"
  },
  {
    "path": "docs/docs/components/hover-card.md",
    "content": "---\ntitle: HoverCard\ndescription: A floating overlay that displays rich content when hovering over a trigger element.\n---\n\n# HoverCard\n\nHoverCard component for displaying rich content that appears when the mouse hovers over a trigger element. Ideal for previewing user profiles, link previews, and other contextual information without requiring a click. Features configurable delays for both opening and closing to prevent flickering during quick mouse movements.\n\nThis is most like the [Popover] component, but triggered by hover instead of click, and with timing controls for a smoother user experience.\n\n## Import\n\n```rust\nuse gpui_component::hover_card::HoverCard;\n```\n\n## Usage\n\n### Basic HoverCard\n\n```rust\nuse gpui::{ParentElement as _, Styled as _};\nuse gpui_component::{hover_card::HoverCard, v_flex};\n\nHoverCard::new(\"basic\")\n    .trigger(\n        div()\n            .child(\"Hover over me\")\n            .text_color(cx.theme().primary)\n            .cursor_pointer()\n            .text_sm()\n    )\n    .child(\n        v_flex()\n            .gap_2()\n            .child(\n                div()\n                    .child(\"This is a hover card\")\n                    .font_semibold()\n                    .text_sm()\n            )\n            .child(\n                div()\n                    .child(\"You can display rich content when hovering over a trigger element.\")\n                    .text_color(cx.theme().muted_foreground)\n                    .text_sm()\n            )\n    )\n```\n\n### User Profile Preview\n\nA common use case is showing user profiles when hovering over a username, similar to GitHub or Twitter:\n\n```rust\nuse gpui::{px, relative, Styled as _};\nuse gpui_component::{\n    avatar::Avatar,\n    hover_card::HoverCard,\n    h_flex,\n    v_flex,\n};\n\nh_flex()\n    .child(\"Hover over \")\n    .text_sm()\n    .child(\n        HoverCard::new(\"user-profile\")\n            .trigger(\n                div()\n                    .child(\"@huacnlee\")\n                    .cursor_pointer()\n                    .text_color(cx.theme().link)\n            )\n            .child(\n                h_flex()\n                    .w(px(320.))\n                    .gap_4()\n                    .items_start()\n                    .child(\n                        Avatar::new()\n                            .src(\"https://avatars.githubusercontent.com/u/5518?s=64\")\n                    )\n                    .child(\n                        v_flex()\n                            .gap_1()\n                            .line_height(relative(1.))\n                            .child(div().child(\"Jason Lee\").font_semibold())\n                            .child(\n                                div()\n                                    .child(\"@huacnlee\")\n                                    .text_color(cx.theme().muted_foreground)\n                                    .text_sm()\n                            )\n                            .child(\"The author of GPUI Component.\")\n                    )\n            )\n    )\n    .child(\" to see their profile\")\n```\n\n### Custom Timing\n\nAdjust the opening and closing delays to suit your needs:\n\n```rust\nuse std::time::Duration;\nuse gpui::Styled as _;\nuse gpui_component::{\n    button::{Button, ButtonVariants as _},\n    h_flex,\n};\n\nh_flex()\n    .gap_4()\n    .child(\n        HoverCard::new(\"fast-open\")\n            .open_delay(Duration::from_millis(200))\n            .close_delay(Duration::from_millis(100))\n            .trigger(Button::new(\"fast\").label(\"Fast Open (200ms)\").outline())\n            .child(div().child(\"This hover card opens after 200ms\").text_sm())\n    )\n    .child(\n        HoverCard::new(\"slow-open\")\n            .open_delay(Duration::from_secs(1))\n            .close_delay(Duration::from_secs_f32(0.5))\n            .trigger(Button::new(\"slow\").label(\"Slow Open (1000ms)\").outline())\n            .child(div().child(\"This hover card opens after 1000ms\").text_sm())\n    )\n```\n\n### Positioning\n\nHoverCard supports 6 positioning options using the [Anchor] type:\n\n- TopLeft\n- TopCenter\n- TopRight\n- BottomLeft\n- BottomCenter\n- BottomRight\n\n### Custom Content Builder\n\nFor performance optimization, you can provide a content builder function for more complex case, which only calls when the HoverCard is opened:\n\n```rust\nHoverCard::new(\"complex\")\n    .trigger(Button::new(\"btn\").label(\"Hover me\"))\n    .content(|state, window, cx| {\n        v_flex()\n            .child(\"Dynamic content\")\n            .child(format!(\"Open: {}\", state.is_open()))\n    })\n```\n\n### Styling\n\nHoverCard inherits all `Styled` trait methods:\n\n```rust\nHoverCard::new(\"styled\")\n    .trigger(Button::new(\"btn\").label(\"Styled\"))\n    .w(px(400.))\n    .max_h(px(500.))\n    .text_sm()\n    .gap_2()\n    .child(\"Styled content\")\n```\n\nDisable default appearance and apply custom styles:\n\n```rust\nHoverCard::new(\"custom-styled\")\n    .appearance(false)  // Disable default popover styling\n    .trigger(Button::new(\"btn\").label(\"Custom\"))\n    .bg(cx.theme().background)\n    .border_2()\n    .border_color(cx.theme().primary)\n    .rounded(px(12.))\n    .p_4()\n    .child(\"Custom styled content\")\n```\n\n## API Reference\n\n### HoverCard Methods\n\n- `new(id: impl Into<ElementId>)` - Create a new HoverCard with a unique ID\n- `trigger<T: IntoElement>(trigger: T)` - Set the element that triggers the hover\n- `content<F>(content: F)` - Set a content builder function that receives `(&mut HoverCardState, &mut Window, &mut Context<HoverCardState>)`\n- `open_delay(duration: Duration)` - Set delay before showing (default: 600ms)\n- `close_delay(duration: Duration)` - Set delay before hiding (default: 300ms)\n- `anchor(anchor: impl Into<Anchor>)` - Set positioning (default: TopCenter)\n- `on_open_change<F>(callback: F)` - Callback when open state changes, receives `(&bool, &mut Window, &mut App)`\n- `appearance(appearance: bool)` - Enable/disable default styling (default: true)\n\n### HoverCardState Methods\n\n- `is_open() -> bool` - Check if the hover card is currently open\n\n## Behavior Details\n\n### Hover Timing\n\nThe HoverCard uses a sophisticated timing system to provide a smooth user experience:\n\n1. **Open Delay (600ms default)**: Prevents the card from flickering when the mouse quickly passes over the trigger\n2. **Close Delay (300ms default)**: Gives users time to move their mouse from the trigger to the content area without the card closing\n3. **Interactive Content**: Users can move their mouse into the content area, and the card will remain open as long as the mouse is either on the trigger or in the content\n\n### Edge Cases Handled\n\n- **Quick Mouse Sweep**: If the mouse quickly moves across the trigger, the card won't open (cancelled by the open delay)\n- **Trigger to Content Movement**: The card stays open when moving the mouse from the trigger to the content area\n- **Rapid Hovering**: Multiple rapid hover events are debounced using an epoch-based timer system\n- **Multiple HoverCards**: Each HoverCard has independent state, so multiple cards can coexist without interfering\n\n## Best Practices\n\n1. **Use appropriate delays**:\n   - Standard content: 600ms open, 300ms close\n   - Quick previews: 500ms open, 200ms close\n   - Tooltips: 300ms open, 100ms close\n\n2. **Keep content concise**: HoverCards should provide preview information, not full content\n\n3. **Make triggers visually distinct**: Use colors, underlines, or cursor changes to indicate hoverable elements\n\n4. **Consider accessibility**: HoverCards are visual-only and don't support keyboard navigation. For keyboard-accessible content, consider using a Popover instead\n\n5. **Avoid nested HoverCards**: They can create confusing user experiences\n\n## Differences from [Popover]\n\n| Feature                  | HoverCard        | Popover            |\n| ------------------------ | ---------------- | ------------------ |\n| Trigger                  | Mouse hover      | Click/right-click  |\n| Keyboard navigation      | No               | Yes (with focus)   |\n| Dismiss on outside click | No               | Yes (configurable) |\n| Timing delays            | Yes (open/close) | No                 |\n| Primary use case         | Previews         | Actions/forms      |\n\n[Popover]: ./popover.md\n[Anchor]: https://docs.rs/gpui-component/latest/gpui_component/enum.Anchor.html\n[Avatar]: ./avatar.md\n"
  },
  {
    "path": "docs/docs/components/icon.md",
    "content": "---\ntitle: Icon\ndescription: Display SVG icons with various sizes, colors, and transformations.\n---\n\n# Icon\n\nA flexible icon component that renders SVG icons from the built-in icon library. Icons are based on Lucide.dev and support customization of size, color, and rotation. The component requires SVG files to be provided by the user in the assets bundle.\n\nBefore you start, please make sure you have read: [Icons & Assets](../assets.md) to understand how use SVG in GPUI & GPUI Component application.\n\n## Import\n\n```rust\nuse gpui_component::{Icon, IconName};\n```\n\n## Usage\n\n### Basic Icon\n\n```rust\n// Using IconName enum directly\nIconName::Heart\n\n// Or creating an Icon explicitly\nIcon::new(IconName::Heart)\n```\n\n### Icon with Custom Size\n\n```rust\n// Predefined sizes\nIcon::new(IconName::Search).xsmall()   // size_3()\nIcon::new(IconName::Search).small()    // size_3p5()\nIcon::new(IconName::Search).medium()   // size_4() (default)\nIcon::new(IconName::Search).large()    // size_6()\n\n// Custom pixel size\nIcon::new(IconName::Search).with_size(px(20.))\n```\n\n### Icon with Custom Color\n\n```rust\n// Using theme colors\nIcon::new(IconName::Heart)\n    .text_color(cx.theme().red)\n\n// Using custom colors\nIcon::new(IconName::Star)\n    .text_color(gpui::red())\n```\n\n### Rotated Icons\n\n```rust\nuse gpui::Radians;\n\n// Rotate by radians\nIcon::new(IconName::ArrowUp)\n    .rotate(Radians::from_degrees(90.))\n\n// Transform with custom transformation\nIcon::new(IconName::ChevronRight)\n    .transform(Transformation::rotate(Radians::PI))\n```\n\n### Custom SVG Path\n\n```rust\n// Using a custom SVG file from assets\nIcon::new(Icon::empty())\n    .path(\"icons/my-custom-icon.svg\")\n```\n\n## Available Icons\n\nThe `IconName` enum provides access to a curated set of icons. Here are some commonly used ones:\n\n### Navigation\n\n- `ArrowUp`, `ArrowDown`, `ArrowLeft`, `ArrowRight`\n- `ChevronUp`, `ChevronDown`, `ChevronLeft`, `ChevronRight`\n- `ChevronsUpDown`\n\n### Actions\n\n- `Check`, `Close`, `Plus`, `Minus`\n- `Copy`, `Delete`, `Search`, `Replace`\n- `Maximize`, `Minimize`, `WindowRestore`\n\n### Files & Folders\n\n- `File`, `Folder`, `FolderOpen`, `FolderClosed`\n- `BookOpen`, `Inbox`\n\n### UI Elements\n\n- `Menu`, `Settings`, `Settings2`, `Ellipsis`, `EllipsisVertical`\n- `Eye`, `EyeOff`, `Bell`, `Info`\n\n### Social & External\n\n- `GitHub`, `Globe`, `ExternalLink`\n- `Heart`, `HeartOff`, `Star`, `StarOff`\n- `ThumbsUp`, `ThumbsDown`\n\n### Status & Alerts\n\n- `CircleCheck`, `CircleX`, `TriangleAlert`\n- `Loader`, `LoaderCircle`\n\n### Panels & Layout\n\n- `PanelLeft`, `PanelRight`, `PanelBottom`\n- `PanelLeftOpen`, `PanelRightOpen`, `PanelBottomOpen`\n- `LayoutDashboard`, `Frame`\n\n### Users & Profile\n\n- `User`, `CircleUser`, `Bot`\n\n### Other\n\n- `Calendar`, `Map`, `Palette`, `Inspector`\n- `Sun`, `Moon`, `Building2`\n\n## Icon Sizes\n\nThe Icon component supports several predefined sizes:\n\n| Size        | Method                | CSS Class    | Pixels |\n| ----------- | --------------------- | ------------ | ------ |\n| Extra Small | `.xsmall()`           | `size_3()`   | 12px   |\n| Small       | `.small()`            | `size_3p5()` | 14px   |\n| Medium      | `.medium()` (default) | `size_4()`   | 16px   |\n| Large       | `.large()`            | `size_6()`   | 24px   |\n| Custom      | `.with_size(px(n))`   | -            | n px   |\n\n## Build you own `IconName`.\n\nYou can define your own `IconName` to have more specific icons for your application. We have `IconNamed` trait for you to implement for your.\n\n```rust\nuse gpui_component::IconNamed;\n\npub enum IconName {\n    Encounters,\n    Monsters,\n    Spells,\n}\n\nimpl IconNamed for IconName {\n    fn path(self) -> gpui::SharedString {\n        match self {\n            IconName::Encounters => \"icons/encounters.svg\",\n            IconName::Monsters => \"icons/monsters.svg\",\n            IconName::Spells => \"icons/spells.svg\",\n        }\n        .into()\n    }\n}\n\n// This allows for the following interactions (works with anything that has the `.icon(icon)` method.\nButton::new(\"my-button\").icon(IconName::Spells);\nIcon::new(IconName::Monsters);\n```\n\nIf you want to directly `render` a custom `IconName` you must implement the `RenderOnce` trait and derive `IntoElement` on the `IconName`.\n\n```rust\nimpl RenderOnce for IconName {\n    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {\n        Icon::empty().path(self.path())\n    }\n}\n\n// Now you can use it directly in your element tree:\ndiv()\n    .child(IconName::Monsters)\n```\n\n## Examples\n\n### Icon in Button\n\n```rust\nuse gpui_component::button::Button;\n\nButton::new(\"like-btn\")\n    .icon(\n        Icon::new(IconName::Heart)\n            .text_color(cx.theme().red)\n            .large()\n    )\n    .label(\"Like\")\n```\n\n### Animated Loading Icon\n\n```rust\nIcon::new(IconName::LoaderCircle)\n    .text_color(cx.theme().muted_foreground)\n    .medium()\n    // Add rotation animation in your render logic\n```\n\n### Status Icons\n\n```rust\n// Success\nIcon::new(IconName::CircleCheck)\n    .text_color(cx.theme().green)\n\n// Error\nIcon::new(IconName::CircleX)\n    .text_color(cx.theme().red)\n\n// Warning\nIcon::new(IconName::TriangleAlert)\n    .text_color(cx.theme().yellow)\n```\n\n### Navigation Icons\n\n```rust\n// Back button\nIcon::new(IconName::ArrowLeft)\n    .medium()\n    .text_color(cx.theme().foreground)\n\n// Dropdown indicator\nIcon::new(IconName::ChevronDown)\n    .small()\n    .text_color(cx.theme().muted_foreground)\n```\n\n### Custom Icon from Assets\n\n```rust\n// Using a custom SVG file\nIcon::empty()\n    .path(\"icons/my-brand-logo.svg\")\n    .large()\n    .text_color(cx.theme().primary)\n```\n\n## Notes\n\n- Icons are rendered as SVG elements and support full CSS styling\n- The default size matches the current text size if no explicit size is set\n- Icons are flex-shrink-0 by default to prevent unwanted shrinking in flex layouts\n- All icon paths are relative to the assets bundle root\n- Icons from Lucide.dev are designed to work well at 16px and scale nicely to other sizes\n"
  },
  {
    "path": "docs/docs/components/image.md",
    "content": "---\ntitle: Image\ndescription: A flexible image display component with loading states, fallbacks, and responsive sizing options.\n---\n\n# Image\n\nThe Image component provides a robust way to display images with comprehensive fallback handling, loading states, and responsive sizing. Built on GPUI's native image support, it handles various image sources including URLs, local files, and SVG graphics with proper error handling and accessibility features.\n\n## Import\n\n```rust\nuse gpui::{img, ImageSource, ObjectFit};\nuse gpui_component::{v_flex, h_flex, div, Icon, IconName};\n```\n\n## Usage\n\n### Basic Image\n\n```rust\n// Simple image from URL\nimg(\"https://example.com/image.jpg\")\n\n// Local image file\nimg(\"assets/logo.png\")\n\n// SVG image\nimg(\"icons/star.svg\")\n```\n\n### Image with Sizing\n\n```rust\n// Fixed dimensions\nimg(\"https://example.com/photo.jpg\")\n    .w(px(300.))\n    .h(px(200.))\n\n// Responsive width with aspect ratio\nimg(\"https://example.com/banner.jpg\")\n    .w(relative(1.))  // Full width\n    .max_w(px(800.))\n    .h(px(400.))\n\n// Square image\nimg(\"https://example.com/avatar.jpg\")\n    .size(px(100.))  // 100x100px\n```\n\n### Object Fit Options\n\nControl how images are scaled and positioned within their containers:\n\n```rust\n// Cover - scales to fill container, may crop\nimg(\"https://example.com/photo.jpg\")\n    .w(px(300.))\n    .h(px(200.))\n    .object_fit(ObjectFit::Cover)\n\n// Contain - scales to fit within container, preserves aspect ratio\nimg(\"https://example.com/photo.jpg\")\n    .w(px(300.))\n    .h(px(200.))\n    .object_fit(ObjectFit::Contain)\n\n// Fill - stretches to fill container, may distort\nimg(\"https://example.com/photo.jpg\")\n    .w(px(300.))\n    .h(px(200.))\n    .object_fit(ObjectFit::Fill)\n\n// Scale Down - acts like contain, but never scales up\nimg(\"https://example.com/photo.jpg\")\n    .w(px(300.))\n    .h(px(200.))\n    .object_fit(ObjectFit::ScaleDown)\n\n// None - original size, may overflow or be smaller than container\nimg(\"https://example.com/photo.jpg\")\n    .w(px(300.))\n    .h(px(200.))\n    .object_fit(ObjectFit::None)\n```\n\n### Image with Fallback Handling\n\n```rust\n// Basic fallback with placeholder\nfn image_with_fallback(src: &str, alt_text: &str) -> impl IntoElement {\n    div()\n        .w(px(300.))\n        .h(px(200.))\n        .bg(cx.theme().surface)\n        .border_1()\n        .border_color(cx.theme().border)\n        .rounded(px(8.))\n        .overflow_hidden()\n        .child(\n            img(src)\n                .w_full()\n                .h_full()\n                .object_fit(ObjectFit::Cover)\n                // Add error handling in practice\n        )\n}\n\n// Fallback with icon placeholder\nfn image_with_icon_fallback(src: &str) -> impl IntoElement {\n    div()\n        .size(px(200.))\n        .bg(cx.theme().surface)\n        .border_1()\n        .border_color(cx.theme().border)\n        .rounded(px(8.))\n        .flex()\n        .items_center()\n        .justify_center()\n        .child(\n            img(src)\n                .size_full()\n                .object_fit(ObjectFit::Cover)\n                // On error, show icon:\n                // Icon::new(IconName::Image)\n                //     .size(px(48.))\n                //     .text_color(cx.theme().muted_foreground)\n        )\n}\n```\n\n### Loading States\n\n```rust\n// Image with loading skeleton\nfn image_with_loading(src: &str, is_loading: bool) -> impl IntoElement {\n    div()\n        .w(px(400.))\n        .h(px(300.))\n        .rounded(px(8.))\n        .overflow_hidden()\n        .map(|this| {\n            if is_loading {\n                this.bg(cx.theme().muted)\n                    .flex()\n                    .items_center()\n                    .justify_center()\n                    .child(\"Loading...\")\n            } else {\n                this.child(\n                    img(src)\n                        .w_full()\n                        .h_full()\n                        .object_fit(ObjectFit::Cover)\n                )\n            }\n        })\n}\n\n// Progressive loading with placeholder\nfn progressive_image(src: &str, placeholder_src: &str) -> impl IntoElement {\n    div()\n        .relative()\n        .w(px(400.))\n        .h(px(300.))\n        .rounded(px(8.))\n        .overflow_hidden()\n        .child(\n            // Low-quality placeholder\n            img(placeholder_src)\n                .absolute()\n                .inset_0()\n                .w_full()\n                .h_full()\n                .object_fit(ObjectFit::Cover)\n                .opacity(0.5)\n        )\n        .child(\n            // High-quality image\n            img(src)\n                .absolute()\n                .inset_0()\n                .w_full()\n                .h_full()\n                .object_fit(ObjectFit::Cover)\n        )\n}\n```\n\n### Responsive Images\n\n```rust\n// Responsive grid images\nfn responsive_image_grid() -> impl IntoElement {\n    div()\n        .grid()\n        .grid_cols(3)\n        .gap_4()\n        .child(\n            img(\"https://example.com/photo1.jpg\")\n                .w_full()\n                .aspect_ratio(1.0)  // Square aspect ratio\n                .object_fit(ObjectFit::Cover)\n                .rounded(px(8.))\n        )\n        .child(\n            img(\"https://example.com/photo2.jpg\")\n                .w_full()\n                .aspect_ratio(1.0)\n                .object_fit(ObjectFit::Cover)\n                .rounded(px(8.))\n        )\n        .child(\n            img(\"https://example.com/photo3.jpg\")\n                .w_full()\n                .aspect_ratio(1.0)\n                .object_fit(ObjectFit::Cover)\n                .rounded(px(8.))\n        )\n}\n\n// Hero image with text overlay\nfn hero_image() -> impl IntoElement {\n    div()\n        .relative()\n        .w_full()\n        .h(px(500.))\n        .rounded(px(12.))\n        .overflow_hidden()\n        .child(\n            img(\"https://example.com/hero-image.jpg\")\n                .absolute()\n                .inset_0()\n                .w_full()\n                .h_full()\n                .object_fit(ObjectFit::Cover)\n        )\n        .child(\n            div()\n                .absolute()\n                .inset_0()\n                .bg(rgba(0, 0, 0, 0.4))  // Dark overlay\n                .flex()\n                .items_center()\n                .justify_center()\n                .child(\n                    v_flex()\n                        .items_center()\n                        .gap_4()\n                        .child(\"Hero Title\")\n                        .child(\"Subtitle text here\")\n                )\n        )\n}\n```\n\n### Image Gallery\n\n```rust\n// Simple image gallery\nfn image_gallery(images: Vec<&str>) -> impl IntoElement {\n    v_flex()\n        .gap_6()\n        .child(\n            // Main image\n            div()\n                .w_full()\n                .h(px(400.))\n                .rounded(px(12.))\n                .overflow_hidden()\n                .child(\n                    img(images[0])\n                        .w_full()\n                        .h_full()\n                        .object_fit(ObjectFit::Cover)\n                )\n        )\n        .child(\n            // Thumbnail row\n            h_flex()\n                .gap_3()\n                .children(\n                    images.iter().map(|src| {\n                        div()\n                            .size(px(80.))\n                            .rounded(px(6.))\n                            .overflow_hidden()\n                            .border_2()\n                            .border_color(cx.theme().border)\n                            .cursor_pointer()\n                            .hover(|this| this.border_color(cx.theme().primary))\n                            .child(\n                                img(*src)\n                                    .size_full()\n                                    .object_fit(ObjectFit::Cover)\n                            )\n                    })\n                )\n        )\n}\n```\n\n### SVG Images\n\n```rust\n// SVG icon with custom styling\nimg(\"assets/icons/logo.svg\")\n    .size(px(64.))\n    .text_color(cx.theme().primary)  // SVG color\n\n// Inline SVG handling\nimg(\"data:image/svg+xml;base64,...\")\n    .w(px(32.))\n    .h(px(32.))\n\n// SVG with animation-friendly setup\nimg(\"assets/spinner.svg\")\n    .size(px(24.))\n    .text_color(cx.theme().primary)\n    // Add rotation animation in practice\n```\n\n## API Reference\n\n### Core Image Function\n\n| Function      | Description                           |\n| ------------- | ------------------------------------- |\n| `img(source)` | Create image element from ImageSource |\n\n### Image Sources (ImageSource)\n\n| Type        | Description            | Example                           |\n| ----------- | ---------------------- | --------------------------------- |\n| String/&str | URL or file path       | `\"https://example.com/image.jpg\"` |\n| SharedUri   | Shared URI reference   | `SharedUri::from(\"file://path\")`  |\n| Local Path  | Local file system path | `\"assets/logo.png\"`               |\n| Data URI    | Base64 encoded image   | `\"data:image/png;base64,...\"`     |\n\n### Sizing Methods\n\n| Method          | Description               |\n| --------------- | ------------------------- |\n| `w(length)`     | Set width                 |\n| `h(length)`     | Set height                |\n| `size(length)`  | Set both width and height |\n| `w_full()`      | Full width of container   |\n| `h_full()`      | Full height of container  |\n| `size_full()`   | Full size of container    |\n| `max_w(length)` | Maximum width             |\n| `max_h(length)` | Maximum height            |\n| `min_w(length)` | Minimum width             |\n| `min_h(length)` | Minimum height            |\n\n### Object Fit Options\n\n| Value                  | Description                       |\n| ---------------------- | --------------------------------- |\n| `ObjectFit::Cover`     | Scale to fill container, may crop |\n| `ObjectFit::Contain`   | Scale to fit within container     |\n| `ObjectFit::Fill`      | Stretch to fill container         |\n| `ObjectFit::ScaleDown` | Like contain, but never scale up  |\n| `ObjectFit::None`      | Original size                     |\n\n### Styling Methods\n\n| Method                | Description             |\n| --------------------- | ----------------------- |\n| `rounded(radius)`     | Border radius           |\n| `border_1()`          | 1px border              |\n| `border_color(color)` | Border color            |\n| `opacity(value)`      | Image opacity (0.0-1.0) |\n| `shadow_sm()`         | Small shadow            |\n| `shadow_lg()`         | Large shadow            |\n\n## Examples\n\n### Product Image Card\n\n```rust\nuse gpui_component::{v_flex, div, Icon, IconName};\n\nfn product_card(image_src: &str, title: &str, price: &str) -> impl IntoElement {\n    v_flex()\n        .gap_3()\n        .p_4()\n        .bg(cx.theme().card)\n        .rounded(px(12.))\n        .shadow_sm()\n        .child(\n            div()\n                .relative()\n                .w_full()\n                .h(px(200.))\n                .rounded(px(8.))\n                .overflow_hidden()\n                .bg(cx.theme().muted)\n                .child(\n                    img(image_src)\n                        .w_full()\n                        .h_full()\n                        .object_fit(ObjectFit::Cover)\n                )\n                .child(\n                    // Wishlist button\n                    div()\n                        .absolute()\n                        .top_2()\n                        .right_2()\n                        .size(px(32.))\n                        .bg(rgba(255, 255, 255, 0.9))\n                        .rounded_full()\n                        .flex()\n                        .items_center()\n                        .justify_center()\n                        .cursor_pointer()\n                        .child(Icon::new(IconName::Heart).size(px(16.)))\n                )\n        )\n        .child(title)\n        .child(price)\n}\n```\n\n### Avatar with Image\n\n```rust\nfn custom_avatar(src: &str, name: &str, size: f32) -> impl IntoElement {\n    div()\n        .size(px(size))\n        .rounded_full()\n        .overflow_hidden()\n        .border_2()\n        .border_color(cx.theme().background)\n        .shadow_sm()\n        .child(\n            img(src)\n                .size_full()\n                .object_fit(ObjectFit::Cover)\n        )\n}\n```\n\n### Image Comparison Slider\n\n```rust\nfn image_comparison(before_src: &str, after_src: &str) -> impl IntoElement {\n    div()\n        .relative()\n        .w_full()\n        .h(px(400.))\n        .rounded(px(12.))\n        .overflow_hidden()\n        .child(\n            // Before image\n            img(before_src)\n                .absolute()\n                .inset_0()\n                .w_full()\n                .h_full()\n                .object_fit(ObjectFit::Cover)\n        )\n        .child(\n            // After image with clip\n            div()\n                .absolute()\n                .top_0()\n                .left_0()\n                .w(relative(0.5))  // Show 50% initially\n                .h_full()\n                .overflow_hidden()\n                .child(\n                    img(after_src)\n                        .w(px(800.))  // Full width of container\n                        .h_full()\n                        .object_fit(ObjectFit::Cover)\n                )\n        )\n        .child(\n            // Divider line\n            div()\n                .absolute()\n                .top_0()\n                .left(relative(0.5))\n                .w(px(2.))\n                .h_full()\n                .bg(cx.theme().primary)\n        )\n}\n```\n\n### Error Handling Pattern\n\n```rust\nenum ImageState {\n    Loading,\n    Loaded,\n    Error,\n}\n\nfn robust_image(src: &str, state: ImageState) -> impl IntoElement {\n    div()\n        .w(px(300.))\n        .h(px(200.))\n        .bg(cx.theme().muted)\n        .rounded(px(8.))\n        .border_1()\n        .border_color(cx.theme().border)\n        .flex()\n        .items_center()\n        .justify_center()\n        .map(|this| {\n            match state {\n                ImageState::Loading => {\n                    this.child(\n                        v_flex()\n                            .items_center()\n                            .gap_2()\n                            .child(Icon::new(IconName::Loader2).size(px(24.)))\n                            .child(\"Loading...\")\n                    )\n                }\n                ImageState::Loaded => {\n                    this.p_0()\n                        .overflow_hidden()\n                        .child(\n                            img(src)\n                                .w_full()\n                                .h_full()\n                                .object_fit(ObjectFit::Cover)\n                        )\n                }\n                ImageState::Error => {\n                    this.child(\n                        v_flex()\n                            .items_center()\n                            .gap_2()\n                            .child(\n                                Icon::new(IconName::ImageOff)\n                                    .size(px(32.))\n                                    .text_color(cx.theme().muted_foreground)\n                            )\n                            .child(\"Failed to load image\")\n                    )\n                }\n            }\n        })\n}\n```\n\n## Best Practices\n\n### Image Optimization\n\n- Use appropriate image dimensions for display size\n- Compress images without sacrificing quality\n- Consider using modern image formats (WebP, AVIF)\n- Implement responsive images for different screen sizes\n\n### Error Handling\n\n- Always provide meaningful fallbacks for failed image loads\n- Use skeleton loading states to maintain layout stability\n- Implement retry mechanisms for temporary network failures\n- Provide user feedback for permanent load failures\n\n### Performance\n\n- Use lazy loading for images not immediately visible\n- Implement proper caching strategies\n- Consider using placeholder images during loading\n- Optimize image sizes for their display context\n\n### User Experience\n\n- Maintain consistent aspect ratios in image grids\n- Provide smooth loading transitions\n- Use appropriate object-fit values for content type\n- Consider providing zoom functionality for detailed images\n\n## Implementation Notes\n\n### GPUI Integration\n\n- Built on GPUI's native image rendering capabilities\n- Supports all GPUI ImageSource types automatically\n- Inherits GPUI's styling and layout system\n- Compatible with GPUI's animation and interaction systems\n\n### SVG Support\n\n- Full support for SVG graphics with proper scaling\n- SVG images can be styled with text colors for theming\n- Vector graphics maintain sharpness at all sizes\n- Supports both external SVG files and inline data URIs\n\n### Memory Management\n\n- GPUI handles image caching and memory management automatically\n- Large images are efficiently managed by the graphics backend\n- No manual memory cleanup required for image components\n\n### Cross-Platform Compatibility\n\n- Consistent behavior across Windows, macOS, and Linux\n- Native image format support varies by platform\n- Uses platform-optimized rendering where available\n"
  },
  {
    "path": "docs/docs/components/index.md",
    "content": "---\ntitle: Components\norder: 2\ncollapsed: false\n---\n\n# Components\n\n### Basic Components\n\n- [Accordion](accordion) - Collapsible content panels\n- [Alert](alert) - Alert messages with different variants\n- [Avatar](avatar) - User avatars with fallback text\n- [Badge](badge) - Count badges and indicators\n- [Button](button) - Interactive buttons with multiple variants\n- [Checkbox](checkbox) - Binary selection control\n- [Collapsible](collapsible) - Expandable/collapsible content\n- [DropdownButton](dropdown_button) - Button with dropdown menu\n- [Icon](icon) - Icon display component\n- [Image](image) - Image display with fallbacks\n- [Kbd](kbd) - Keyboard shortcut display\n- [Label](label) - Text labels for form elements\n- [Pagination](pagination) - Page navigation controls\n- [Progress](progress) - Progress bars\n- [Radio](radio) - Single selection from multiple options\n- [Rating](rating) - Interactive star rating component\n- [Skeleton](skeleton) - Loading placeholders\n- [Slider](slider) - Value selection from a range\n- [Spinner](spinner) - Loading and status spinners\n- [Stepper](stepper) - Step-by-step progress indicator\n- [Switch](switch) - Toggle on/off control\n- [Tag](tag) - Labels and categories\n- [Toggle](toggle) - Toggle button states\n- [Tooltip](tooltip) - Helpful hints on hover\n\n### Form Components\n\n- [Input](input) - An input field or a component that looks like an input field.\n- [Select](select) - A list of options for the user to pick.\n- [NumberInput](number-input) - Numeric input with increment/decrement\n- [DatePicker](date-picker) - Date selection with calendar\n- [OtpInput](otp-input) - One-time password input\n- [ColorPicker](color-picker) - Color selection interface\n- [Editor](editor) - Multi-line text editor and code editor\n- [Form](form) - Form container and layout\n\n### Layout Components\n\n- [DescriptionList](description-list) - Key-value pair display\n- [GroupBox](group-box) - Grouped content with borders\n- [Dialog](dialog) - Dialog and modal windows\n- [Notification](notification) - Toast notifications\n- [Popover](popover) - Floating content display\n- [Resizable](resizable) - Resizable panels and containers\n- [Scrollable](scrollable) - Scrollable containers\n- [Sheet](sheet) - Slide-in panel from edges\n- [Sidebar](sidebar) - Navigation sidebar\n\n### Advanced Components\n\n- [Calendar](calendar) - Calendar display and navigation\n- [Chart](chart) - Data visualization charts (Line, Bar, Area, Pie, Candlestick)\n- [List](list) - List display with items\n- [Menu](menu) - Menu and context menu and dropdown menu.\n- [Settings](settings) - Settings UI\n- [DataTable](data-table) - High-performance data tables\n- [Tabs](tabs) - Tabbed interface\n- [Tree](tree) - Hierarchical tree data display\n- [VirtualList](virtual-list) - Virtualized list for large datasets\n"
  },
  {
    "path": "docs/docs/components/input.md",
    "content": "---\ntitle: Input\ndescription: Text input component with validation, masking, and various features.\n---\n\n# Input\n\nA flexible text input component with support for validation, masking, prefix/suffix elements, and different states.\n\n## Import\n\n```rust\nuse gpui_component::input::{InputState, Input};\n```\n\n## Usage\n\n### Basic Input\n\n```rust\nlet input = cx.new(|cx| InputState::new(window, cx));\n\nInput::new(&input)\n```\n\n### With Placeholder\n\n```rust\nlet input = cx.new(|cx|\n    InputState::new(window, cx)\n        .placeholder(\"Enter your name...\")\n);\n\nInput::new(&input)\n```\n\n### With Default Value\n\n```rust\nlet input = cx.new(|cx|\n    InputState::new(window, cx)\n        .default_value(\"John Doe\")\n);\n\nInput::new(&input)\n```\n\n### Cleanable Input\n\n```rust\nInput::new(&input)\n    .cleanable(true) // Show clear button when input has value\n```\n\n### With Prefix and Suffix\n\n```rust\nuse gpui_component::{Icon, IconName};\n\n// With prefix icon\nInput::new(&input)\n    .prefix(Icon::new(IconName::Search).small())\n\n// With suffix button\nInput::new(&input)\n    .suffix(\n        Button::new(\"info\")\n            .ghost()\n            .icon(IconName::Info)\n            .xsmall()\n    )\n\n// With both\nInput::new(&input)\n    .prefix(Icon::new(IconName::Search).small())\n    .suffix(Button::new(\"btn\").ghost().icon(IconName::Info).xsmall())\n```\n\n### Password Input (Masked)\n\n```rust\nlet input = cx.new(|cx|\n    InputState::new(window, cx)\n        .masked(true)\n        .default_value(\"password123\")\n);\n\nInput::new(&input)\n    .mask_toggle() // Shows toggle button to reveal password\n```\n\n### Input Sizes\n\n```rust\nInput::new(&input).large()\nInput::new(&input) // medium (default)\nInput::new(&input).small()\n```\n\n### Disabled Input\n\n```rust\nInput::new(&input).disabled(true)\n```\n\n### Clean on ESC\n\n```rust\nlet input = cx.new(|cx|\n    InputState::new(window, cx)\n        .clean_on_escape() // Clear input when ESC is pressed\n);\n\nInput::new(&input)\n```\n\n### Input Validation\n\n```rust\n// Validate float numbers\nlet input = cx.new(|cx|\n    InputState::new(window, cx)\n        .validate(|s, _| s.parse::<f32>().is_ok())\n);\n\n// Regex pattern validation\nlet input = cx.new(|cx|\n    InputState::new(window, cx)\n        .pattern(regex::Regex::new(r\"^[a-zA-Z0-9]*$\").unwrap())\n);\n```\n\n### Input Masking\n\n```rust\n// Phone number mask\nlet input = cx.new(|cx|\n    InputState::new(window, cx)\n        .mask_pattern(\"(999)-999-9999\")\n);\n\n// Custom pattern: AAA-###-AAA (A=letter, #=digit, 9=digit optional)\nlet input = cx.new(|cx|\n    InputState::new(window, cx)\n        .mask_pattern(\"AAA-###-AAA\")\n);\n\n// Number with thousands separator\nuse gpui_component::input::MaskPattern;\n\nlet input = cx.new(|cx|\n    InputState::new(window, cx)\n        .mask_pattern(MaskPattern::Number {\n            separator: Some(','),\n            fraction: Some(3),\n        })\n);\n```\n\n### Handle Input Events\n\n```rust\nlet input = cx.new(|cx| InputState::new(window, cx));\n\ncx.subscribe_in(&input, window, |view, state, event, window, cx| {\n    match event {\n        InputEvent::Change => {\n            let text = state.read(cx).value();\n            println!(\"Input changed: {}\", text);\n        }\n        InputEvent::PressEnter { secondary } => {\n            println!(\"Enter pressed, secondary: {}\", secondary);\n        }\n        InputEvent::Focus => println!(\"Input focused\"),\n        InputEvent::Blur => println!(\"Input blurred\"),\n    }\n});\n```\n\n### Custom Appearance\n\n```rust\n// Without default styling\nInput::new(&input).appearance(false)\n\n// Use in custom container\ndiv()\n    .border_b_2()\n    .px_6()\n    .py_3()\n    .border_color(cx.theme().border)\n    .bg(cx.theme().secondary)\n    .child(Input::new(&input).appearance(false))\n```\n\n## Examples\n\n### Search Input\n\n```rust\nlet search = cx.new(|cx|\n    InputState::new(window, cx)\n        .placeholder(\"Search...\")\n);\n\nInput::new(&search)\n    .prefix(Icon::new(IconName::Search).small())\n```\n\n### Currency Input\n\n```rust\nlet amount = cx.new(|cx|\n    InputState::new(window, cx)\n        .mask_pattern(MaskPattern::Number {\n            separator: Some(','),\n            fraction: Some(2),\n        })\n);\n\ndiv()\n    .child(Input::new(&amount))\n    .child(format!(\"Value: {}\", amount.read(cx).value()))\n```\n\n### Form with Multiple Inputs\n\n```rust\nstruct FormView {\n    name_input: Entity<InputState>,\n    email_input: Entity<InputState>,\n}\n\nv_flex()\n    .gap_3()\n    .child(Input::new(&self.name_input))\n    .child(Input::new(&self.email_input))\n```\n"
  },
  {
    "path": "docs/docs/components/kbd.md",
    "content": "---\ntitle: Kbd\ndescription: Displays keyboard shortcuts with platform-specific formatting.\n---\n\n# Kbd\n\nA component for displaying keyboard shortcuts and key combinations with proper platform-specific formatting. Automatically adapts the display to match the conventions of macOS (using symbols) or Windows/Linux (using text labels).\n\n## Import\n\n```rust\nuse gpui_component::kbd::Kbd;\nuse gpui::Keystroke;\n```\n\n## Usage\n\n### Basic Keyboard Shortcut\n\n```rust\n// Create from a keystroke\nlet kbd = Kbd::new(Keystroke::parse(\"cmd-shift-p\").unwrap());\n\n// Or convert directly from keystroke\nlet kbd: Kbd = Keystroke::parse(\"escape\").unwrap().into();\n```\n\n### Common Shortcuts\n\n```rust\n// Command palette\nKbd::new(Keystroke::parse(\"cmd-shift-p\").unwrap())\n\n// New tab\nKbd::new(Keystroke::parse(\"cmd-t\").unwrap())\n\n// Zoom controls\nKbd::new(Keystroke::parse(\"cmd--\").unwrap())  // Zoom out\nKbd::new(Keystroke::parse(\"cmd-+\").unwrap())  // Zoom in\n\n// Navigation\nKbd::new(Keystroke::parse(\"escape\").unwrap())\nKbd::new(Keystroke::parse(\"enter\").unwrap())\nKbd::new(Keystroke::parse(\"backspace\").unwrap())\n```\n\n### Multiple Modifiers\n\n```rust\n// Complex combinations\nKbd::new(Keystroke::parse(\"cmd-ctrl-shift-a\").unwrap())\nKbd::new(Keystroke::parse(\"cmd-alt-backspace\").unwrap())\nKbd::new(Keystroke::parse(\"ctrl-alt-shift-a\").unwrap())\n```\n\n### Arrow Keys and Function Keys\n\n```rust\n// Arrow keys\nKbd::new(Keystroke::parse(\"left\").unwrap())\nKbd::new(Keystroke::parse(\"right\").unwrap())\nKbd::new(Keystroke::parse(\"up\").unwrap())\nKbd::new(Keystroke::parse(\"down\").unwrap())\n\n// Function keys\nKbd::new(Keystroke::parse(\"f12\").unwrap())\nKbd::new(Keystroke::parse(\"secondary-f12\").unwrap())\n\n// Page navigation\nKbd::new(Keystroke::parse(\"pageup\").unwrap())\nKbd::new(Keystroke::parse(\"pagedown\").unwrap())\n```\n\n### Without Visual Styling\n\n```rust\n// Display only the key text without the styled background\nKbd::new(Keystroke::parse(\"cmd-s\").unwrap())\n    .appearance(false)\n```\n\n### From Action Bindings\n\n```rust\nuse gpui::{Action, Window, FocusHandle};\n\n// Get first keybinding for an action\nif let Some(kbd) = Kbd::binding_for_action(&MyAction {}, None, window) {\n    // Display the bound shortcut\n}\n\n// Get keybinding for action within a specific context\nif let Some(kbd) = Kbd::binding_for_action(&MyAction {}, Some(\"Editor\"), window) {\n    // Display context-specific shortcut\n}\n\n// Get keybinding for action within a focus handle\nif let Some(kbd) = Kbd::binding_for_action_in(&MyAction {}, &focus_handle, window) {\n    // Display shortcut for focused element\n}\n```\n\n## Platform Differences\n\nThe Kbd component automatically formats shortcuts according to platform conventions:\n\n### macOS\n\n- Uses symbols: ⌃ ⌥ ⇧ ⌘\n- No separators between modifiers\n- Order: Control, Option, Shift, Command\n- Special keys: ⌫ (backspace), ⎋ (escape), ⏎ (enter), ← → ↑ ↓ (arrows)\n\n### Windows/Linux\n\n- Uses text labels: Ctrl, Alt, Shift, Win\n- Plus sign (+) separators\n- Order: Ctrl, Alt, Shift, Win\n- Special keys: Backspace, Esc, Enter, Left, Right, Up, Down\n\n### Examples by Platform\n\n| Input               | macOS | Windows/Linux     |\n| ------------------- | ----- | ----------------- |\n| `cmd-a`             | ⌘A    | Win+A             |\n| `ctrl-shift-a`      | ⌃⇧A   | Ctrl+Shift+A      |\n| `cmd-alt-backspace` | ⌥⌘⌫   | Win+Alt+Backspace |\n| `escape`            | ⎋     | Esc               |\n| `enter`             | ⏎     | Enter             |\n| `left`              | ←     | Left              |\n\n## Examples\n\n### Keyboard Shortcut Help\n\n```rust\nuse gpui::{div, h_flex, v_flex};\n\n// Display common shortcuts\nv_flex()\n    .gap_2()\n    .child(\n        h_flex()\n            .gap_2()\n            .items_center()\n            .child(\"Open command palette:\")\n            .child(Kbd::new(Keystroke::parse(\"cmd-shift-p\").unwrap()))\n    )\n    .child(\n        h_flex()\n            .gap_2()\n            .items_center()\n            .child(\"Save file:\")\n            .child(Kbd::new(Keystroke::parse(\"cmd-s\").unwrap()))\n    )\n    .child(\n        h_flex()\n            .gap_2()\n            .items_center()\n            .child(\"Find in files:\")\n            .child(Kbd::new(Keystroke::parse(\"cmd-shift-f\").unwrap()))\n    )\n```\n\n### Menu Item with Shortcut\n\n```rust\nh_flex()\n    .justify_between()\n    .items_center()\n    .child(\"New File\")\n    .child(Kbd::new(Keystroke::parse(\"cmd-n\").unwrap()))\n```\n\n### Inline Documentation\n\n```rust\ndiv()\n    .child(\"Press \")\n    .child(Kbd::new(Keystroke::parse(\"escape\").unwrap()))\n    .child(\" to cancel or \")\n    .child(Kbd::new(Keystroke::parse(\"enter\").unwrap()))\n    .child(\" to confirm.\")\n```\n\n### Custom Styling\n\n```rust\nKbd::new(Keystroke::parse(\"cmd-k\").unwrap())\n    .text_color(cx.theme().accent)\n    .border_color(cx.theme().accent)\n    .bg(cx.theme().accent.opacity(0.1))\n```\n\n### Text-Only Format\n\n```rust\n// Get formatted text without styling\nlet shortcut_text = Kbd::format(&Keystroke::parse(\"cmd-shift-p\").unwrap());\ndiv().child(format!(\"Shortcut: {}\", shortcut_text))\n```\n\n## Styling\n\nThe Kbd component uses the following default styles:\n\n- Border with theme border color\n- Muted foreground text color\n- Background with theme background color\n- Small rounded corners\n- Centered text alignment\n- Extra small font size\n- Minimal padding (0.5px vertical, 1px horizontal)\n- Minimum width of 5 units\n- Flex shrink disabled to maintain size\n\nAll styles can be customized using the `Styled` trait methods.\n"
  },
  {
    "path": "docs/docs/components/label.md",
    "content": "---\ntitle: Label\ndescription: Text labels for form elements with highlighting and styling options.\n---\n\n# Label\n\nA versatile label component for displaying text with support for secondary text, highlighting, masking, and customizable styling. Perfect for form labels, captions, and general text display with optional/required indicators.\n\n## Import\n\n```rust\nuse gpui_component::label::{Label, HighlightsMatch};\n```\n\n## Usage\n\n### Basic Label\n\n```rust\nLabel::new(\"This is a label\")\n```\n\n### Label with Secondary Text\n\n```rust\n// Label with optional indicator\nLabel::new(\"Company Address\")\n    .secondary(\"(optional)\")\n\n// Label with required indicator\nLabel::new(\"Email Address\")\n    .secondary(\"(required)\")\n```\n\n### Text Alignment\n\n```rust\n// Left aligned (default)\nLabel::new(\"Text align left\")\n\n// Center aligned\nLabel::new(\"Text align center\")\n    .text_center()\n\n// Right aligned\nLabel::new(\"Text align right\")\n    .text_right()\n```\n\n### Text Highlighting\n\n```rust\n// Full text highlighting (finds all matches)\nLabel::new(\"Hello World Hello\")\n    .highlights(\"Hello\")\n\n// Prefix highlighting (only matches at start)\nLabel::new(\"Hello World\")\n    .highlights(HighlightsMatch::Prefix(\"Hello\".into()))\n\n// Highlight with secondary text\nLabel::new(\"Company Name\")\n    .secondary(\"(optional)\")\n    .highlights(\"Company\")\n```\n\n### Color and Styling\n\n```rust\nuse gpui_component::green_500;\n\n// Custom text color\nLabel::new(\"Color Label\")\n    .text_color(green_500())\n\n// Font styling\nLabel::new(\"Font Size Label\")\n    .text_size(px(20.))\n    .font_semibold()\n    .line_height(rems(1.8))\n```\n\n### Masked Labels\n\n```rust\n// For sensitive information\nLabel::new(\"9,182,1 USD\")\n    .text_2xl()\n    .masked(true) // Shows as \"•••••••••••\"\n\n// Toggle masking programmatically\nLabel::new(\"500 USD\")\n    .text_xl()\n    .masked(self.masked)\n```\n\n### Multi-line Text\n\n```rust\n// Text wrapping with line height\ndiv().w(px(200.)).child(\n    Label::new(\n        \"Label should support text wrap in default, \\\n        if the text is too long, it should wrap to the next line.\"\n    )\n    .line_height(rems(1.8))\n)\n```\n\n### Different Sizes\n\n```rust\n// Using text size utilities\nLabel::new(\"Extra Large\").text_2xl()\nLabel::new(\"Large\").text_xl()\nLabel::new(\"Medium\").text_base() // default\nLabel::new(\"Small\").text_sm()\nLabel::new(\"Extra Small\").text_xs()\n```\n\n## API Reference\n\n### Label\n\n| Method              | Description                                                   |\n| ------------------- | ------------------------------------------------------------- |\n| `new(text)`         | Create a new label with text                                  |\n| `secondary(text)`   | Add secondary text (usually for optional/required indicators) |\n| `masked(bool)`      | Show/hide text with bullet characters                         |\n| `highlights(match)` | Highlight matching text                                       |\n\n### HighlightsMatch\n\n| Variant        | Description                                      |\n| -------------- | ------------------------------------------------ |\n| `Full(text)`   | Highlights all occurrences of the text           |\n| `Prefix(text)` | Highlights only if text appears at the beginning |\n\n| Method        | Description                     |\n| ------------- | ------------------------------- |\n| `as_str()`    | Get the search text as string   |\n| `is_prefix()` | Check if this is a prefix match |\n\n### Styling Methods (via Styled trait)\n\n| Method                | Description                 |\n| --------------------- | --------------------------- |\n| `text_color(color)`   | Set text color              |\n| `text_size(size)`     | Set font size               |\n| `text_center()`       | Center align text           |\n| `text_right()`        | Right align text            |\n| `font_semibold()`     | Set font weight to semibold |\n| `font_bold()`         | Set font weight to bold     |\n| `line_height(height)` | Set line height             |\n| `text_xs()`           | Extra small text size       |\n| `text_sm()`           | Small text size             |\n| `text_base()`         | Base text size (default)    |\n| `text_lg()`           | Large text size             |\n| `text_xl()`           | Extra large text size       |\n| `text_2xl()`          | 2x large text size          |\n\n## Examples\n\n### Form Labels\n\n```rust\n// Required field\nLabel::new(\"Email Address\")\n    .secondary(\"*\")\n    .text_color(cx.theme().destructive)\n\n// Optional field\nLabel::new(\"Phone Number\")\n    .secondary(\"(optional)\")\n\n// Field with description\nLabel::new(\"Password\")\n    .secondary(\"(minimum 8 characters)\")\n```\n\n### Search Highlighting\n\n```rust\n// Interactive search highlighting\nlet search_term = \"Hello\";\nLabel::new(\"Hello World Hello Universe\")\n    .highlights(search_term) // Highlights all \"Hello\" occurrences\n```\n\n### Sensitive Information\n\n```rust\n// Financial data with toggle\nh_flex()\n    .child(\n        Label::new(\"$9,182.50 USD\")\n            .text_2xl()\n            .masked(self.is_masked)\n    )\n    .child(\n        Button::new(\"toggle-mask\")\n            .ghost()\n            .icon(if self.is_masked { IconName::EyeOff } else { IconName::Eye })\n            .on_click(|this, _, _, _| {\n                this.is_masked = !this.is_masked;\n            })\n    )\n```\n\n### Multi-language Support\n\n```rust\n// Supports Unicode text\nLabel::new(\"这是一个标签\") // Chinese text\nLabel::new(\"こんにちは世界\") // Japanese text\nLabel::new(\"🌍 Hello World 🚀\") // Emojis\n```\n\n### Status Indicators\n\n```rust\n// Success status\nLabel::new(\"✓ Verified\")\n    .text_color(cx.theme().success)\n\n// Warning status\nLabel::new(\"⚠ Pending Review\")\n    .text_color(cx.theme().warning)\n\n// Error status\nLabel::new(\"✗ Failed\")\n    .text_color(cx.theme().destructive)\n```\n\n### Custom Layouts\n\n```rust\n// Flex layout with labels\nh_flex()\n    .justify_between()\n    .child(Label::new(\"Total Amount\"))\n    .child(Label::new(\"$1,234.56\").font_semibold())\n\n// Grid layout\nv_flex()\n    .gap_2()\n    .child(Label::new(\"Name:\").font_semibold())\n    .child(Label::new(\"John Doe\"))\n    .child(Label::new(\"Email:\").font_semibold())\n    .child(Label::new(\"john@example.com\"))\n```\n"
  },
  {
    "path": "docs/docs/components/list.md",
    "content": "---\ntitle: List\ndescription: A flexible list component that displays a series of items with support for sections, search, selection, and infinite scrolling.\n---\n\n# List\n\nA powerful List component that provides a virtualized, searchable list interface with support for sections, headers, footers, selection states, and infinite scrolling. The component is built on a delegate pattern that allows for flexible data management and custom item rendering.\n\n## Import\n\n```rust\nuse gpui_component::list::{List, ListState, ListDelegate, ListItem, ListEvent, ListSeparatorItem};\nuse gpui_component::IndexPath;\n```\n\n## Usage\n\n### Basic List\n\nTo create a list, you need to implement the `ListDelegate` trait for your data:\n\n```rust\nstruct MyListDelegate {\n    items: Vec<String>,\n    selected_index: Option<IndexPath>,\n}\n\nimpl ListDelegate for MyListDelegate {\n    type Item = ListItem;\n\n    fn items_count(&self, _section: usize, _cx: &App) -> usize {\n        self.items.len()\n    }\n\n    fn render_item(\n        &mut self,\n        ix: IndexPath,\n        _window: &mut Window,\n        _cx: &mut Context<TableState<Self>>,\n    ) -> Option<Self::Item> {\n        self.items.get(ix.row).map(|item| {\n            ListItem::new(ix)\n                .child(Label::new(item.clone()))\n                .selected(Some(ix) == self.selected_index)\n        })\n    }\n\n    fn set_selected_index(\n        &mut self,\n        ix: Option<IndexPath>,\n        _window: &mut Window,\n        cx: &mut Context<ListState<Self>>,\n    ) {\n        self.selected_index = ix;\n        cx.notify();\n    }\n}\n\n// Create the list\nlet delegate = MyListDelegate {\n    items: vec![\"Item 1\".into(), \"Item 2\".into(), \"Item 3\".into()],\n    selected_index: None,\n};\n\n/// Create a list state.\nlet state = cx.new(|cx| ListState::new(delegate, window, cx));\n```\n\nNow use [List] to render list:\n\n```rs\ndiv().child(List::new(&state))\n```\n\n### List with Sections\n\n**Note:** Sections with `items_count` of 0 will be automatically hidden (no header or footer will be rendered for empty sections).\n\n```rust\nimpl ListDelegate for MyListDelegate {\n    type Item = ListItem;\n\n    fn sections_count(&self, _cx: &App) -> usize {\n        3 // Number of sections\n    }\n\n    fn items_count(&self, section: usize, _cx: &App) -> usize {\n        match section {\n            0 => 5,\n            1 => 3,\n            2 => 7,\n            _ => 0,\n        }\n    }\n\n    fn render_section_header(\n        &mut self,\n        section: usize,\n        _window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) -> Option<impl IntoElement> {\n        let title = match section {\n            0 => \"Section 1\",\n            1 => \"Section 2\",\n            2 => \"Section 3\",\n            _ => return None,\n        };\n\n        Some(\n            h_flex()\n                .px_2()\n                .py_1()\n                .gap_2()\n                .text_sm()\n                .text_color(cx.theme().muted_foreground)\n                .child(Icon::new(IconName::Folder))\n                .child(title)\n        )\n    }\n\n    fn render_section_footer(\n        &mut self,\n        section: usize,\n        _window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) -> Option<impl IntoElement> {\n        Some(\n            div()\n                .px_2()\n                .py_1()\n                .text_xs()\n                .text_color(cx.theme().muted_foreground)\n                .child(format!(\"End of section {}\", section + 1))\n        )\n    }\n}\n```\n\n### List Items with Icons and Actions\n\n```rust\nfn render_item(\n    &mut self,\n    ix: IndexPath,\n    _window: &mut Window,\n    cx: &mut Context<TableState<Self>>,\n) -> Option<Self::Item> {\n    self.items.get(ix.row).map(|item| {\n        ListItem::new(ix)\n            .child(\n                h_flex()\n                    .items_center()\n                    .gap_2()\n                    .child(Icon::new(IconName::File))\n                    .child(Label::new(item.title.clone()))\n            )\n            .suffix(|_, _| {\n                Button::new(\"action\")\n                    .ghost()\n                    .small()\n                    .icon(IconName::MoreHorizontal)\n            })\n            .selected(Some(ix) == self.selected_index)\n            .on_click(cx.listener(move |this, _, window, cx| {\n                this.delegate_mut().select_item(ix, window, cx);\n            }))\n    })\n}\n```\n\n### List with Search\n\nThe list automatically includes a search input by default. Implement `perform_search` to handle queries:\n\nAnd you should use `searchable(true)` when creating the `ListState` to show search input.\n\n```rust\nimpl ListDelegate for MyListDelegate {\n    fn perform_search(\n        &mut self,\n        query: &str,\n        _window: &mut Window,\n        _cx: &mut Context<ListState<Self>>,\n    ) -> Task<()> {\n        // Filter items based on query\n        self.filtered_items = self.all_items\n            .iter()\n            .filter(|item| item.to_lowercase().contains(&query.to_lowercase()))\n            .cloned()\n            .collect();\n\n        Task::ready(())\n    }\n}\n\nlet state = cx.new(|cx| ListState::new(delegate, window, cx).searchable(true));\nList::new(&state)\n```\n\n### List with Loading State\n\n```rust\nimpl ListDelegate for MyListDelegate {\n    fn loading(&self, _cx: &App) -> bool {\n        self.is_loading\n    }\n\n    fn render_loading(\n        &mut self,\n        _window: &mut Window,\n        _cx: &mut Context<TableState<Self>>,\n    ) -> impl IntoElement {\n        // Custom loading view\n        v_flex()\n            .justify_center()\n            .items_center()\n            .py_4()\n            .child(Skeleton::new().h_4().w_full())\n            .child(Skeleton::new().h_4().w_3_4())\n    }\n}\n```\n\n### Infinite Scrolling\n\n```rust\nimpl ListDelegate for MyListDelegate {\n    fn has_more(&self, _cx: &App) -> bool {\n        self.has_more_data\n    }\n\n    fn load_more_threshold(&self) -> usize {\n        20 // Trigger when 20 items from bottom\n    }\n\n    fn load_more(&mut self, window: &mut Window, cx: &mut Context<ListState<Self>>) {\n        if self.is_loading {\n            return;\n        }\n\n        self.is_loading = true;\n        cx.spawn_in(window, async move |view, window| {\n            // Simulate API call\n            Timer::after(Duration::from_secs(1)).await;\n\n            view.update_in(window, |view, _, cx| {\n                // Add more items\n                view.delegate_mut().load_more_items();\n                view.delegate_mut().is_loading = false;\n                cx.notify();\n            });\n        }).detach();\n    }\n}\n```\n\n### List Events\n\n```rust\n// Subscribe to list events\nlet _subscription = cx.subscribe(&state, |_, _, event: &ListEvent, _| {\n    match event {\n        ListEvent::Select(ix) => {\n            println!(\"Item selected at: {:?}\", ix);\n        }\n        ListEvent::Confirm(ix) => {\n            println!(\"Item confirmed at: {:?}\", ix);\n        }\n        ListEvent::Cancel => {\n            println!(\"Selection cancelled\");\n        }\n    }\n});\n```\n\n### Different Item Styles\n\n```rust\n// Basic item with hover effect\nListItem::new(ix)\n    .child(Label::new(\"Basic Item\"))\n    .selected(is_selected)\n\n// Item with check icon\nListItem::new(ix)\n    .child(Label::new(\"Checkable Item\"))\n    .check_icon(IconName::Check)\n    .confirmed(is_confirmed)\n\n// Disabled item\nListItem::new(ix)\n    .child(Label::new(\"Disabled Item\"))\n    .disabled(true)\n\n// Separator item\nListSeparatorItem::new()\n    .child(\n        div()\n            .h_px()\n            .w_full()\n            .bg(cx.theme().border)\n    )\n```\n\n### Custom Empty State\n\n```rust\nimpl ListDelegate for MyListDelegate {\n    fn render_empty(&mut self, _window: &mut Window, cx: &mut Context<TableState<Self>>) -> impl IntoElement {\n        v_flex()\n            .size_full()\n            .justify_center()\n            .items_center()\n            .gap_2()\n            .child(Icon::new(IconName::Search).size_16().text_color(cx.theme().muted_foreground))\n            .child(\n                Label::new(\"No items found\")\n                    .text_color(cx.theme().muted_foreground)\n            )\n            .child(\n                Label::new(\"Try adjusting your search terms\")\n                    .text_sm()\n                    .text_color(cx.theme().muted_foreground.opacity(0.7))\n            )\n    }\n}\n```\n\n## Configuration Options\n\n### List Configuration\n\n```rust\nList::new(&state)\n    .max_h(px(400.))                    // Set maximum height\n    .scrollbar_visible(false)           // Hide scrollbar\n    .paddings(Edges::all(px(8.)))       // Set internal padding\n```\n\n### Scrolling Control\n\n```rust\n// Scroll to specific item\nstate.update(cx, |state, cx| {\n    state.scroll_to_item(\n        IndexPath::new(0).section(1),  // Row 0 of section 1\n        ScrollStrategy::Center,\n        window,\n        cx,\n    );\n});\n\n// Scroll to selected item\nstate.update(cx, |state, cx| {\n    state.scroll_to_selected_item(window, cx);\n});\n\n// Set selected index without scrolling\nstate.update(cx, |state, cx| {\n    state.set_selected_index(Some(IndexPath::new(5)), window, cx);\n});\n```\n\n## Examples\n\n### File Browser List\n\n```rust\nstruct FileBrowserDelegate {\n    files: Vec<FileInfo>,\n    selected: Option<IndexPath>,\n}\n\n#[derive(Clone)]\nstruct FileInfo {\n    name: String,\n    is_directory: bool,\n    size: Option<u64>,\n}\n\nimpl ListDelegate for FileBrowserDelegate {\n    type Item = ListItem;\n\n    fn render_item(&mut self, ix: IndexPath, window: &mut Window, cx: &mut Context<TableState<Self>>) -> Option<Self::Item> {\n        self.files.get(ix.row).map(|file| {\n            let icon = if file.is_directory {\n                IconName::Folder\n            } else {\n                IconName::File\n            };\n\n            ListItem::new(ix)\n                .child(\n                    h_flex()\n                        .items_center()\n                        .justify_between()\n                        .w_full()\n                        .child(\n                            h_flex()\n                                .items_center()\n                                .gap_2()\n                                .child(Icon::new(icon))\n                                .child(Label::new(file.name.clone()))\n                        )\n                        .when_some(file.size, |this, size| {\n                            this.child(\n                                Label::new(format_size(size))\n                                    .text_sm()\n                                    .text_color(cx.theme().muted_foreground)\n                            )\n                        })\n                )\n                .selected(Some(ix) == self.selected)\n        })\n    }\n}\n```\n\n### Contact List with Sections\n\n```rust\nstruct ContactListDelegate {\n    contacts_by_letter: BTreeMap<char, Vec<Contact>>,\n    selected: Option<IndexPath>,\n}\n\nimpl ListDelegate for ContactListDelegate {\n    type Item = ListItem;\n\n    fn sections_count(&self, _cx: &App) -> usize {\n        self.contacts_by_letter.len()\n    }\n\n    fn render_section_header(&mut self, section: usize, _window: &mut Window, cx: &mut Context<TableState<Self>>) -> Option<impl IntoElement> {\n        let letter = self.contacts_by_letter.keys().nth(section)?;\n\n        Some(\n            div()\n                .px_3()\n                .py_2()\n                .bg(cx.theme().background)\n                .border_b_1()\n                .border_color(cx.theme().border)\n                .child(\n                    Label::new(letter.to_string())\n                        .text_lg()\n                        .text_color(cx.theme().accent_foreground)\n                        .font_weight(FontWeight::BOLD)\n                )\n        )\n    }\n}\n```\n"
  },
  {
    "path": "docs/docs/components/menu.md",
    "content": "---\ntitle: Menu\ndescription: Context menus and popup menus with support for icons, shortcuts, submenus, and various menu item types.\n---\n\n# PopupMenu\n\nThe Menu component provides both context menus (right-click menus) and popup menus with comprehensive features including icons, keyboard shortcuts, submenus, separators, checkable items, and custom elements. Built with accessibility and keyboard navigation in mind.\n\n## Import\n\n```rust\nuse gpui_component::{\n    menu::{PopupMenu, PopupMenuItem, ContextMenuExt, DropdownMenu},\n    Button\n};\nuse gpui::{actions, Action};\n```\n\n## Usage\n\n### ContextMenu\n\nContext menus appear when right-clicking on an element:\n\n```rust\nuse gpui_component::menu::ContextMenuExt;\n\ndiv()\n    .id(\"my-element\")\n    .child(\"Right click me\")\n    .context_menu(|menu, window, cx| {\n        menu.menu(\"Copy\", Box::new(Copy))\n            .menu(\"Paste\", Box::new(Paste))\n            .separator()\n            .menu(\"Delete\", Box::new(Delete))\n    })\n```\n\n### DropdownMenu\n\nDropdown menus are triggered by buttons or other interactive elements:\n\n```rust\nuse gpui_component::popup_menu::{PopupMenuExt as _, PopupMenuItem};\n\nlet view = cx.entity();\nButton::new(\"menu-btn\")\n    .label(\"Open Menu\")\n    .dropdown_menu(|menu, window, cx| {\n        menu.menu(\"New File\", Box::new(NewFile))\n            .menu(\"Open File\", Box::new(OpenFile))\n            .link(\"Documentation\", \"https://longbridge.github.io/gpui-component/\")\n            .separator()\n            .item(PopupMenuItem::new(\"Custom Action\")\n                .on_click(window.listener_for(&view, |this, _, window, cx| {\n                    // Custom action logic here\n                    this.\n                })\n            )\n            .separator()\n            .menu(\"Exit\", Box::new(Exit))\n    })\n```\n\n:::tip\nAs you see, the each menu item is associated with an [Action],\nwe choice this design to better integrate with GPUI's action and key binding system,\nallowing menu items to automatically display keyboard shortcuts when applicable.\n\nSo, the [Action] is the recommended way to define menu item behaviors.\n\nHowever, if you prefer not to use [Action]s, you can create custom menu items using the `item` method along with [PopupMenuItem].\nThere have a `on_click` callback to handle the click event directly.\n:::\n\n### Anchor Position\n\nControl where the dropdown menu appears relative to the trigger:\n\n```rust\nuse gpui::Corner;\n\nButton::new(\"menu-btn\")\n    .label(\"Options\")\n    .dropdown_menu_with_anchor(Corner::TopRight, |menu, window, cx| {\n        menu.menu(\"Option 1\", Box::new(Action1))\n            .menu(\"Option 2\", Box::new(Action2))\n    })\n```\n\n### Icons\n\nAdd icons to menu items for better visual clarity:\n\n```rust\nuse gpui_component::IconName;\n\nmenu.menu_with_icon(\"Search\", IconName::Search, Box::new(Search))\n    .menu_with_icon(\"Settings\", IconName::Settings, Box::new(OpenSettings))\n    .separator()\n    .menu_with_icon(\"Help\", IconName::Help, Box::new(ShowHelp))\n```\n\n### Disabled State\n\nCreate disabled menu items that cannot be activated:\n\n```rust\nmenu.menu(\"Available Action\", Box::new(Action1))\n    .menu_with_disabled(\"Disabled Action\", Box::new(Action2), true)\n    .menu_with_icon_and_disabled(\n        \"Unavailable\",\n        IconName::Lock,\n        Box::new(Action3),\n        true\n    )\n```\n\n### Check state\n\nCreate menu items that show a check state:\n\n```rust\nlet is_enabled = true;\n\nmenu.menu_with_check(\"Enable Feature\", is_enabled, Box::new(ToggleFeature))\n    .menu_with_check(\"Show Sidebar\", sidebar_visible, Box::new(ToggleSidebar))\n```\n\nBy default, the check icon will be shown on the left side of the menu item, if this menu item has an icon, the check icon will replace the icon on the left side.\n\nThere also have a `check_side` option for you to config the check icon to be shown on the right side:\n\n```rust\nmenu.check_size(Side::Right)\n    .menu_with_check(\"Enable Feature\", is_enabled, Box::new(ToggleFeature))\n```\n\n### Separators\n\nUse separators to group related menu items:\n\n```rust\nmenu.menu(\"New\", Box::new(NewFile))\n    .menu(\"Open\", Box::new(OpenFile))\n    .separator()  // Groups file operations\n    .menu(\"Copy\", Box::new(Copy))\n    .menu(\"Paste\", Box::new(Paste))\n    .separator()  // Groups edit operations\n    .menu(\"Exit\", Box::new(Exit))\n```\n\n### Labels\n\nAdd non-interactive labels to organize menu sections:\n\n```rust\nmenu.label(\"File Operations\")\n    .menu(\"New\", Box::new(NewFile))\n    .menu(\"Open\", Box::new(OpenFile))\n    .separator()\n    .label(\"Edit Operations\")\n    .menu(\"Copy\", Box::new(Copy))\n    .menu(\"Paste\", Box::new(Paste))\n```\n\n### Link MenuItem\n\nCreate menu items that open external links:\n\n```rust\nmenu.link(\"Documentation\", \"https://docs.example.com\")\n    .link_with_icon(\n        \"GitHub\",\n        IconName::GitHub,\n        \"https://github.com/example/repo\"\n    )\n    .separator()\n    .external_link_icon(false) // Hide external link icons\n    .link(\"Support\", \"https://support.example.com\")\n```\n\n### Custom Element\n\nCreate custom menu items with complex content:\n\n```rust\nuse gpui_component::{h_flex, v_flex};\n\nmenu.menu_element(Box::new(CustomAction), |window, cx| {\n        v_flex()\n            .child(\"Custom Element\")\n            .child(\n                div()\n                    .text_xs()\n                    .text_color(cx.theme().muted_foreground)\n                    .child(\"This is a subtitle\")\n            )\n    })\n    .menu_element_with_icon(\n        IconName::Info,\n        Box::new(InfoAction),\n        |window, cx| {\n            h_flex()\n                .gap_1()\n                .child(\"Status\")\n                .child(\n                    div()\n                        .text_sm()\n                        .text_color(cx.theme().success)\n                        .child(\"✓ Connected\")\n                )\n        }\n    )\n```\n\n### Keyboard Shortcuts\n\nMenu items automatically display keyboard shortcuts if they're bound to actions:\n\n```rust\n// First define your actions and key bindings\nactions!(my_app, [Copy, Paste, Cut]);\n\n// In your app initialization\ncx.bind_keys([\n    KeyBinding::new(\"ctrl-c\", Copy, Some(\"editor\")),\n    KeyBinding::new(\"ctrl-v\", Paste, Some(\"editor\")),\n    KeyBinding::new(\"ctrl-x\", Cut, Some(\"editor\")),\n]);\n\n// The menu will automatically show shortcuts\nmenu.action_context(focus_handle) // Set context for shortcuts\n    .menu(\"Copy\", Box::new(Copy))     // Will show \"Ctrl+C\"\n    .menu(\"Paste\", Box::new(Paste))   // Will show \"Ctrl+V\"\n    .menu(\"Cut\", Box::new(Cut))       // Will show \"Ctrl+X\"\n```\n\n### Submenus\n\nCreate nested menus with submenu support:\n\n```rust\nmenu.submenu(\"File\", window, cx, |submenu, window, cx| {\n        submenu.menu(\"New\", Box::new(NewFile))\n            .menu(\"Open\", Box::new(OpenFile))\n            .separator()\n            .menu(\"Recent\", Box::new(ShowRecent))\n    })\n    .submenu(\"Edit\", window, cx, |submenu, window, cx| {\n        submenu.menu(\"Undo\", Box::new(Undo))\n            .menu(\"Redo\", Box::new(Redo))\n    })\n```\n\n### Submenus with Icons\n\nAdd icons to submenu headers:\n\n```rust\nmenu.submenu_with_icon(\n        Some(IconName::Folder.into()),\n        \"Project\",\n        window,\n        cx,\n        |submenu, window, cx| {\n            submenu.menu(\"Open Project\", Box::new(OpenProject))\n                .menu(\"Close Project\", Box::new(CloseProject))\n        }\n    )\n```\n\n### Scrollable Menus\n\n:::warning\nWhen you have enabled `scrollable()` on a menu, avoid using submenus within it, as this can lead to usability issues.\n:::\n\nFor menus with many items, enable scrolling:\n\n```rust\nButton::new(\"large-menu\")\n    .label(\"Many Options\")\n    .dropdown_menu(|menu, window, cx| {\n        let mut menu = menu\n            .scrollable(true)\n            .max_h(px(300.))\n            .label(\"Select an option\");\n\n        for i in 0..100 {\n            menu = menu.menu(\n                format!(\"Option {}\", i),\n                Box::new(SelectOption(i))\n            );\n        }\n        menu\n    })\n```\n\n### Menu Sizing\n\nControl menu dimensions:\n\n```rust\nmenu.min_w(px(200.))      // Minimum width\n    .max_w(px(400.))      // Maximum width\n    .max_h(px(300.))      // Maximum height\n    .scrollable(true)         // Enable scrolling when content exceeds max height\n```\n\n### Action Context\n\nSet the focus context for handling menu actions:\n\n```rust\nlet focus_handle = cx.focus_handle();\n\nmenu.action_context(focus_handle)\n    .menu(\"Copy\", Box::new(Copy))\n    .menu(\"Paste\", Box::new(Paste))\n```\n\n## API Reference\n\n- [PopupMenu]\n- [context_menu]\n- [PopupMenuItem]\n\n## Examples\n\n### File Manager Context Menu\n\n```rust\ndiv()\n    .id(\"file-manager\")\n    .child(\"Right-click for options\")\n    .context_menu(|menu, window, cx| {\n        menu.menu_with_icon(\"Open\", IconName::FolderOpen, Box::new(Open))\n            .separator()\n            .menu_with_icon(\"Copy\", IconName::Copy, Box::new(Copy))\n            .menu_with_icon(\"Cut\", IconName::Scissors, Box::new(Cut))\n            .menu_with_icon(\"Paste\", IconName::Clipboard, Box::new(Paste))\n            .separator()\n            .submenu(\"New\", window, cx, |submenu, window, cx| {\n                submenu.menu_with_icon(\"File\", IconName::File, Box::new(NewFile))\n                    .menu_with_icon(\"Folder\", IconName::Folder, Box::new(NewFolder))\n            })\n            .separator()\n            .menu_with_icon(\"Delete\", IconName::Trash, Box::new(Delete))\n            .separator()\n            .menu(\"Properties\", Box::new(ShowProperties))\n    })\n```\n\n### Add MenuItem without action\n\nSometimes you may not like to define an action for a menu item, you just want add a `on_click` handler, in this case, the `item` and [PopupMenuItem] can help you:\n\n```rust\nuse gpui_component::{menu::PopupMenuItem, Button};\n\nButton::new(\"custom-item-menu\")\n    .label(\"Options\")\n    .dropdown_menu(|menu, window, cx| {\n        menu.item(\n            PopupMenuItem::new(\"Custom Action\")\n                .disabled(false)\n                .icon(IconName::Star)\n                .on_click(|window, cx| {\n                    // Custom click handler logic\n                    println!(\"Custom Action Clicked!\");\n                })\n        )\n        .separator()\n        .menu(\"Standard Action\", Box::new(StandardAction))\n    })\n```\n\n### Editor Menu with Shortcuts\n\n```rust\n// Define actions with keyboard shortcuts\nactions!(editor, [Save, SaveAs, Find, Replace, ToggleWordWrap]);\n\n// Set up key bindings\ncx.bind_keys([\n    KeyBinding::new(\"ctrl-s\", Save, Some(\"editor\")),\n    KeyBinding::new(\"ctrl-shift-s\", SaveAs, Some(\"editor\")),\n    KeyBinding::new(\"ctrl-f\", Find, Some(\"editor\")),\n    KeyBinding::new(\"ctrl-h\", Replace, Some(\"editor\")),\n]);\n\n// Create menu with automatic shortcuts\nlet editor_focus = cx.focus_handle();\n\nButton::new(\"editor-menu\")\n    .label(\"Edit\")\n    .dropdown_menu(|menu, window, cx| {\n        menu.action_context(editor_focus)\n            .menu(\"Save\", Box::new(Save))           // Shows \"Ctrl+S\"\n            .menu(\"Save As...\", Box::new(SaveAs))   // Shows \"Ctrl+Shift+S\"\n            .separator()\n            .menu(\"Find\", Box::new(Find))           // Shows \"Ctrl+F\"\n            .menu(\"Replace\", Box::new(Replace))     // Shows \"Ctrl+H\"\n            .separator()\n            .menu_with_check(\"Word Wrap\", true, Box::new(ToggleWordWrap))\n    })\n```\n\n### Settings Menu with Custom Elements\n\n```rust\nButton::new(\"settings\")\n    .label(\"Settings\")\n    .dropdown_menu(|menu, window, cx| {\n        menu.label(\"Display\")\n            .menu_element_with_check(dark_mode, Box::new(ToggleDarkMode), |window, cx| {\n                h_flex()\n                    .gap_2()\n                    .child(\"Dark Mode\")\n                    .child(\n                        div()\n                            .text_xs()\n                            .text_color(cx.theme().muted_foreground)\n                            .child(if dark_mode { \"On\" } else { \"Off\" })\n                    )\n            })\n            .separator()\n            .label(\"Account\")\n            .menu_element_with_icon(\n                IconName::User,\n                Box::new(ShowProfile),\n                |window, cx| {\n                    v_flex()\n                        .child(\"John Doe\")\n                        .child(\n                            div()\n                                .text_xs()\n                                .text_color(cx.theme().muted_foreground)\n                                .child(\"john@example.com\")\n                        )\n                }\n            )\n            .separator()\n            .link_with_icon(\"Help Center\", IconName::Help, \"https://help.example.com\")\n            .menu(\"Sign Out\", Box::new(SignOut))\n    })\n```\n\n## Keyboard Shortcuts\n\n| Key               | Action                            |\n| ----------------- | --------------------------------- |\n| `↑` / `↓`         | Navigate menu items               |\n| `←` / `→`         | Navigate submenus                 |\n| `Enter` / `Space` | Activate menu item                |\n| `Escape`          | Close menu                        |\n| `Tab`             | Close menu and focus next element |\n\n## Best Practices\n\n1. **Group Related Items**: Use separators to group related functionality\n2. **Consistent Icons**: Use consistent iconography across your application\n3. **Logical Order**: Place most common actions at the top\n4. **Keyboard Shortcuts**: Provide shortcuts for frequently used actions\n5. **Context Awareness**: Show only relevant items for the current context\n6. **Progressive Disclosure**: Use submenus for complex hierarchies\n7. **Clear Labels**: Use descriptive, action-oriented labels\n8. **Reasonable Limits**: Use scrollable menus for more than 10-15 items\n\n[PopupMenu]: https://docs.rs/gpui-component/latest/gpui_component/menu/struct.PopupMenu.html\n[PopupMenuItem]: https://docs.rs/gpui-component/latest/gpui_component/menu/struct.PopupMenuItem.html\n[context_menu]: https://docs.rs/gpui-component/latest/gpui_component/menu/trait.ContextMenuExt.html#method.context_menu\n[Action]: https://docs.rs/gpui/latest/gpui/trait.Action.html\n"
  },
  {
    "path": "docs/docs/components/notification.md",
    "content": "---\ntitle: Notification\ndescription: Display toast notifications that appear at the top right of the window with auto-dismiss functionality.\n---\n\n# Notification\n\nA toast notification system for displaying temporary messages to users. Notifications appear at the top right of the window and can auto-dismiss after a timeout. Supports multiple variants (info, success, warning, error), custom content, titles, and action buttons. Perfect for status updates, confirmations, and user feedback.\n\n## Import\n\n```rust\nuse gpui_component::{\n    notification::{Notification, NotificationType},\n    WindowExt\n};\n```\n\n## Usage\n\n### Setup application root view for display of notifications\n\nYou need to set up your application's root view to render the notification layer. This is typically done in your main application struct's render method.\n\nThe [Root::render_notification_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_notification_layer) function handles rendering any active modals on top of your app content.\n\n```rust\nuse gpui_component::{TitleBar, Root};\n\nstruct Example {}\n\nimpl Render for Example {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let notification_layer = Root::render_notification_layer(window, cx);\n\n        div()\n            .size_full()\n            .child(\n                v_flex()\n                    .size_full()\n                    .child(TitleBar::new())\n                    .child(div().flex_1().child(\"Hello world!\")),\n            )\n            // Render the notification layer on top of the app content\n            .children(notification_layer)\n    }\n}\n```\n\n### Basic Notification\n\n```rust\n// Simple string notification\nwindow.push_notification(\"This is a notification.\", cx);\n\n// Using Notification builder\nNotification::new()\n    .message(\"Your changes have been saved.\")\n```\n\n### Notification Types\n\n```rust\n// Info notification (blue)\nwindow.push_notification(\n    (NotificationType::Info, \"File saved successfully.\"),\n    cx,\n);\n\n// Success notification (green)\nwindow.push_notification(\n    (NotificationType::Success, \"Payment processed successfully.\"),\n    cx,\n);\n\n// Warning notification (yellow/orange)\nwindow.push_notification(\n    (NotificationType::Warning, \"Network connection is unstable.\"),\n    cx,\n);\n\n// Error notification (red)\nwindow.push_notification(\n    (NotificationType::Error, \"Failed to save file. Please try again.\"),\n    cx,\n);\n```\n\n### Notification with Title\n\n```rust\nNotification::new()\n    .title(\"Update Available\")\n    .message(\"A new version of the application is ready to install.\")\n    .with_type(NotificationType::Info)\n```\n\n### Auto-hide Control\n\n```rust\n// Disable auto-hide (manual dismiss only)\nNotification::new()\n    .message(\"This notification stays until manually closed.\")\n    .autohide(false)\n\n// Default auto-hide after 5 seconds\nNotification::new()\n    .message(\"This will disappear automatically.\")\n    .autohide(true) // default\n```\n\n### With Action Button\n\n```rust\nNotification::new()\n    .title(\"Connection Lost\")\n    .message(\"Unable to connect to server.\")\n    .with_type(NotificationType::Error)\n    .autohide(false)\n    .action(|_, cx| {\n        Button::new(\"retry\")\n            .primary()\n            .label(\"Retry\")\n            .on_click(cx.listener(|this, _, window, cx| {\n                // Perform retry action\n                println!(\"Retrying connection...\");\n                this.dismiss(window, cx);\n            }))\n    })\n```\n\n### Clickable Notifications\n\n```rust\nNotification::new()\n    .message(\"Click to view details\")\n    .on_click(cx.listener(|_, _, _, cx| {\n        println!(\"Notification clicked\");\n        // Handle notification click\n        cx.notify();\n    }))\n```\n\n### Custom Content\n\n```rust\nuse gpui_component::text::markdown;\n\nlet markdown_content = r#\"\n## Custom Notification\n- **Feature**: New dashboard available\n- **Status**: Ready to use\n- [Learn more](https://example.com)\n\"#;\n\nNotification::new()\n    .content(|_, window, cx| {\n        markdown(markdown_content).into_any_element()\n    })\n```\n\n### Unique Notifications\n\nWhen you need to manage notifications manually, such as for long-running processes or persistent alerts, you can use unique IDs to push and remove notifications as needed.\n\nIn this case, you can create a special `struct` in local scope, and use `id` methods with this struct to identify the notification.\n\nThen you can push the notification when needed, and later remove it using the same ID.\n\nLike this:\n\n```rust\n// Using type-based ID for uniqueness\nstruct UpdateNotification;\n\nNotification::new()\n    .id::<UpdateNotification>()\n    .message(\"System update available\")\n    .autohide(false)\n\n// Using type + element ID for multiple unique notifications\nstruct TaskNotification;\n\nNotification::warning(\"Task failed to complete\")\n    .id1::<TaskNotification>(\"task-123\")\n    .title(\"Task Failed\")\n```\n\nThen remove the notification with `window.remove_notification::<UpdateNotification>`, like this:\n\n```rust\n// Later, dismiss the notification\nwindow.remove_notification::<UpdateNotification>(cx);\n```\n\n## Examples\n\n### Form Validation Error\n\n```rust\nNotification::error(\"Please correct the following errors before submitting.\")\n    .title(\"Validation Failed\")\n    .autohide(false)\n    .action(|_, _, cx| {\n        Button::new(\"review\")\n            .outline()\n            .label(\"Review Form\")\n            .on_click(cx.listener(|this, _, window, cx| {\n                // Navigate to form\n                this.dismiss(window, cx);\n            }))\n    })\n```\n\n### File Upload Progress\n\n```rust\nstruct UploadNotification;\n\n// Start upload notification\nwindow.push_notification(\n    Notification::info(\"Uploading file...\")\n        .id::<UploadNotification>()\n        .title(\"File Upload\")\n        .autohide(false),\n    cx,\n);\n\n// Update to success when complete\nwindow.push_notification(\n    Notification::success(\"File uploaded successfully!\")\n        .id::<UploadNotification>()\n        .title(\"Upload Complete\"),\n    cx,\n);\n```\n\n### System Status Updates\n\n```rust\n// Warning about maintenance\nNotification::warning(\"System maintenance will begin in 30 minutes.\")\n    .title(\"Scheduled Maintenance\")\n    .autohide(false)\n    .action(|_, cx| {\n        Button::new(\"details\")\n            .link()\n            .label(\"View Details\")\n            .on_click(cx.listener(|this, _, window, cx| {\n                // Show maintenance details\n                this.dismiss(window, cx);\n            }))\n    })\n```\n\n### Batch Operation Results\n\n```rust\nuse gpui_component::text::markdown;\n\nlet results_content = r#\"\n## Batch Operation Complete\n\n**Processed**: 150 items\n**Success**: 147 items\n**Failed**: 3 items\n\n[View failed items](/)\n\"#;\n\nNotification::success(\"Batch operation completed with some failures.\")\n    .title(\"Operation Results\")\n    .content(|window, cx| {\n        markdown(results_content).into_any_element()\n    })\n    .autohide(false)\n```\n\n### Interactive Confirmation\n\n```rust\nstruct SaveConfirmation;\n\nNotification::new()\n    .id::<SaveConfirmation>()\n    .title(\"Unsaved Changes\")\n    .message(\"You have unsaved changes. Save before leaving?\")\n    .autohide(false)\n    .action(|_, cx| {\n        Button::new(\"save\")\n            .primary()\n            .label(\"Save\")\n            .on_click(cx.listener(|this, _, window, cx| {\n                // Perform save\n                println!(\"Saving changes...\");\n                this.dismiss(window, cx);\n            }))\n    })\n    .on_click(cx.listener(|_, _, _, cx| {\n        println!(\"Save reminder clicked\");\n        cx.notify();\n    }))\n```\n"
  },
  {
    "path": "docs/docs/components/number-input.md",
    "content": "---\ntitle: NumberInput\ndescription: Number input component with increment/decrement controls and numeric formatting.\n---\n\n# NumberInput\n\nA specialized input component for numeric values with built-in increment/decrement buttons and support for min/max values, step values, and number formatting with thousands separators.\n\n## Import\n\n```rust\nuse gpui_component::input::{InputState, NumberInput, NumberInputEvent, StepAction};\n```\n\n## Usage\n\n### Basic Number Input\n\n```rust\nlet number_input = cx.new(|cx|\n    InputState::new(window, cx)\n        .placeholder(\"Enter number\")\n        .default_value(\"1\")\n);\n\nNumberInput::new(&number_input)\n```\n\n### With Min/Max Validation\n\n```rust\n// Integer input with validation\nlet integer_input = cx.new(|cx|\n    InputState::new(window, cx)\n        .placeholder(\"Integer value\")\n        .pattern(Regex::new(r\"^\\d+$\").unwrap()) // Only positive integers\n);\n\nNumberInput::new(&integer_input)\n```\n\n### With Number Formatting\n\n```rust\nuse gpui_component::input::MaskPattern;\n\n// Currency input with thousands separator\nlet currency_input = cx.new(|cx|\n    InputState::new(window, cx)\n        .placeholder(\"Amount\")\n        .mask_pattern(MaskPattern::Number {\n            separator: Some(','),\n            fraction: Some(2), // 2 decimal places\n        })\n);\n\nNumberInput::new(&currency_input)\n```\n\n### Different Sizes\n\n```rust\n// Large size\nNumberInput::new(&input).large()\n\n// Medium size (default)\nNumberInput::new(&input)\n\n// Small size\nNumberInput::new(&input).small()\n```\n\n### With Prefix and Suffix\n\n```rust\nuse gpui_component::{button::{Button, ButtonVariants}, IconName};\n\n// With currency prefix\nNumberInput::new(&input)\n    .prefix(div().child(\"$\"))\n\n// With info button suffix\nNumberInput::new(&input)\n    .suffix(\n        Button::new(\"info\")\n            .ghost()\n            .icon(IconName::Info)\n            .xsmall()\n    )\n```\n\n### Disabled State\n\n```rust\nNumberInput::new(&input).disabled(true)\n```\n\n### Without Default Styling\n\n```rust\n// For custom container styling\ndiv()\n    .w_full()\n    .bg(cx.theme().secondary)\n    .rounded(cx.theme().radius)\n    .child(NumberInput::new(&input).appearance(false))\n```\n\n### Handle Number Input Events\n\n```rust\nlet number_input = cx.new(|cx| InputState::new(window, cx));\nlet mut value: i64 = 0;\n\n// Subscribe to input changes\ncx.subscribe_in(&number_input, window, |view, state, event, window, cx| {\n    match event {\n        InputEvent::Change => {\n            let text = state.read(cx).value();\n            if let Ok(new_value) = text.parse::<i64>() {\n                view.value = new_value;\n            }\n        }\n        _ => {}\n    }\n});\n\n// Subscribe to increment/decrement actions\ncx.subscribe_in(&number_input, window, |view, state, event, window, cx| {\n    match event {\n        NumberInputEvent::Step(step_action) => {\n            match step_action {\n                StepAction::Increment => {\n                    view.value += 1;\n                    state.update(cx, |input, cx| {\n                        input.set_value(view.value.to_string(), window, cx);\n                    });\n                }\n                StepAction::Decrement => {\n                    view.value -= 1;\n                    state.update(cx, |input, cx| {\n                        input.set_value(view.value.to_string(), window, cx);\n                    });\n                }\n            }\n        }\n    }\n});\n```\n\n### Programmatic Control\n\n```rust\n// Increment programmatically\nNumberInput::increment(&number_input, window, cx);\n\n// Decrement programmatically\nNumberInput::decrement(&number_input, window, cx);\n```\n\n## API Reference\n\n### NumberInput\n\n| Method                         | Description                                |\n| ------------------------------ | ------------------------------------------ |\n| `new(state)`                   | Create number input with InputState entity |\n| `placeholder(str)`             | Set placeholder text                       |\n| `size(size)`                   | Set input size (small, medium, large)      |\n| `prefix(el)`                   | Add prefix element                         |\n| `suffix(el)`                   | Add suffix element                         |\n| `appearance(bool)`             | Enable/disable default styling             |\n| `disabled(bool)`               | Set disabled state                         |\n| `increment(state, window, cx)` | Increment value programmatically           |\n| `decrement(state, window, cx)` | Decrement value programmatically           |\n\n### NumberInputEvent\n\n| Event              | Description                        |\n| ------------------ | ---------------------------------- |\n| `Step(StepAction)` | Increment/decrement button pressed |\n\n### StepAction\n\n| Action      | Description               |\n| ----------- | ------------------------- |\n| `Increment` | Value should be increased |\n| `Decrement` | Value should be decreased |\n\n### InputState (Number-specific methods)\n\n| Method                              | Description                                             |\n| ----------------------------------- | ------------------------------------------------------- |\n| `pattern(regex)`                    | Set regex pattern for validation (e.g., digits only)    |\n| `mask_pattern(MaskPattern::Number)` | Set number formatting with separator and decimal places |\n| `value()`                           | Get current display value (formatted)                   |\n| `unmask_value()`                    | Get actual numeric value (unformatted)                  |\n\n### MaskPattern::Number\n\n| Field       | Type            | Description                            |\n| ----------- | --------------- | -------------------------------------- |\n| `separator` | `Option<char>`  | Thousands separator (e.g., ',' or ' ') |\n| `fraction`  | `Option<usize>` | Number of decimal places               |\n\n## Keyboard Navigation\n\n| Key         | Action                     |\n| ----------- | -------------------------- |\n| `↑`         | Increment value            |\n| `↓`         | Decrement value            |\n| `Tab`       | Navigate to next field     |\n| `Shift+Tab` | Navigate to previous field |\n| `Enter`     | Submit/confirm value       |\n| `Escape`    | Clear input (if enabled)   |\n\n## Examples\n\n### Integer Counter\n\n```rust\nstruct CounterView {\n    counter_input: Entity<InputState>,\n    counter_value: i32,\n}\n\nimpl CounterView {\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let counter_input = cx.new(|cx|\n            InputState::new(window, cx)\n                .placeholder(\"Count\")\n                .default_value(\"0\")\n                .pattern(Regex::new(r\"^-?\\d+$\").unwrap()) // Allow negative integers\n        );\n\n        let _subscription = cx.subscribe_in(&counter_input, window, Self::on_number_event);\n\n        Self {\n            counter_input,\n            counter_value: 0,\n        }\n    }\n\n    fn on_number_event(\n        &mut self,\n        state: &Entity<InputState>,\n        event: &NumberInputEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        match event {\n            NumberInputEvent::Step(StepAction::Increment) => {\n                self.counter_value += 1;\n                state.update(cx, |input, cx| {\n                    input.set_value(self.counter_value.to_string(), window, cx);\n                });\n            }\n            NumberInputEvent::Step(StepAction::Decrement) => {\n                self.counter_value -= 1;\n                state.update(cx, |input, cx| {\n                    input.set_value(self.counter_value.to_string(), window, cx);\n                });\n            }\n        }\n    }\n}\n\n// Usage\nNumberInput::new(&self.counter_input)\n```\n\n### Currency Input\n\n```rust\nstruct PriceInput {\n    price_input: Entity<InputState>,\n    price_value: f64,\n}\n\nimpl PriceInput {\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let price_input = cx.new(|cx|\n            InputState::new(window, cx)\n                .placeholder(\"0.00\")\n                .mask_pattern(MaskPattern::Number {\n                    separator: Some(','),\n                    fraction: Some(2),\n                })\n        );\n\n        Self {\n            price_input,\n            price_value: 0.0,\n        }\n    }\n}\n\n// Usage with currency prefix\nh_flex()\n    .gap_2()\n    .child(div().child(\"$\"))\n    .child(NumberInput::new(&self.price_input))\n```\n\n### Quantity Selector with Limits\n\n```rust\nstruct QuantitySelector {\n    quantity_input: Entity<InputState>,\n    quantity: u32,\n    min_quantity: u32,\n    max_quantity: u32,\n}\n\nimpl QuantitySelector {\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let min_quantity = 1;\n        let max_quantity = 99;\n\n        let quantity_input = cx.new(|cx|\n            InputState::new(window, cx)\n                .default_value(min_quantity.to_string())\n                .pattern(Regex::new(&format!(r\"^[{}-{}]\\d*$\", min_quantity, max_quantity)).unwrap())\n        );\n\n        let _subscription = cx.subscribe_in(&quantity_input, window, Self::on_quantity_event);\n\n        Self {\n            quantity_input,\n            quantity: min_quantity,\n            min_quantity,\n            max_quantity,\n        }\n    }\n\n    fn on_quantity_event(\n        &mut self,\n        state: &Entity<InputState>,\n        event: &NumberInputEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        match event {\n            NumberInputEvent::Step(StepAction::Increment) => {\n                if self.quantity < self.max_quantity {\n                    self.quantity += 1;\n                    state.update(cx, |input, cx| {\n                        input.set_value(self.quantity.to_string(), window, cx);\n                    });\n                }\n            }\n            NumberInputEvent::Step(StepAction::Decrement) => {\n                if self.quantity > self.min_quantity {\n                    self.quantity -= 1;\n                    state.update(cx, |input, cx| {\n                        input.set_value(self.quantity.to_string(), window, cx);\n                    });\n                }\n            }\n        }\n    }\n}\n\n// Usage\nNumberInput::new(&self.quantity_input).small()\n```\n\n### Floating Point Input\n\n```rust\nstruct FloatInput {\n    float_input: Entity<InputState>,\n    float_value: f64,\n    step: f64,\n}\n\nimpl FloatInput {\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let float_input = cx.new(|cx|\n            InputState::new(window, cx)\n                .placeholder(\"0.0\")\n                .pattern(Regex::new(r\"^-?\\d*\\.?\\d*$\").unwrap()) // Allow decimal numbers\n        );\n\n        Self {\n            float_input,\n            float_value: 0.0,\n            step: 0.1,\n        }\n    }\n\n    fn on_float_event(\n        &mut self,\n        state: &Entity<InputState>,\n        event: &NumberInputEvent,\n        window: &mut Window,\n        cx: &mut Context<Self>,\n    ) {\n        match event {\n            NumberInputEvent::Step(StepAction::Increment) => {\n                self.float_value += self.step;\n                state.update(cx, |input, cx| {\n                    input.set_value(format!(\"{:.1}\", self.float_value), window, cx);\n                });\n            }\n            NumberInputEvent::Step(StepAction::Decrement) => {\n                self.float_value -= self.step;\n                state.update(cx, |input, cx| {\n                    input.set_value(format!(\"{:.1}\", self.float_value), window, cx);\n                });\n            }\n        }\n    }\n}\n```\n\n## Best Practices\n\n1. **Validation**: Always validate numeric input on both client and server side\n2. **Range Limits**: Implement min/max validation for user safety\n3. **Step Size**: Choose appropriate step values for your use case\n4. **Error Handling**: Provide clear feedback for invalid input\n5. **Formatting**: Use consistent number formatting across your application\n6. **Performance**: Debounce rapid increment/decrement actions if needed\n7. **Accessibility**: Always provide proper labels and descriptions\n"
  },
  {
    "path": "docs/docs/components/otp-input.md",
    "content": "---\ntitle: OtpInput\ndescription: One-time password input component with multiple fields, auto-focus, and paste handling.\n---\n\n# OtpInput\n\nA specialized input component for one-time passwords (OTP) that displays multiple input fields in a grid layout. Perfect for SMS verification codes, authenticator app codes, and other numeric verification scenarios.\n\n## Import\n\n```rust\nuse gpui_component::input::{OtpInput, OtpState};\n```\n\n## Usage\n\n### Basic OTP Input\n\n```rust\nlet otp_state = cx.new(|cx| OtpState::new(6, window, cx));\n\nOtpInput::new(&otp_state)\n```\n\n### With Default Value\n\n```rust\nlet otp_state = cx.new(|cx|\n    OtpState::new(6, window, cx)\n        .default_value(\"123456\")\n);\n\nOtpInput::new(&otp_state)\n```\n\n### Masked OTP Input\n\n```rust\nlet otp_state = cx.new(|cx|\n    OtpState::new(6, window, cx)\n        .masked(true)\n        .default_value(\"123456\")\n);\n\nOtpInput::new(&otp_state)\n```\n\n### Different Sizes\n\n```rust\n// Small size\nOtpInput::new(&otp_state).small()\n\n// Medium size (default)\nOtpInput::new(&otp_state)\n\n// Large size\nOtpInput::new(&otp_state).large()\n\n// Custom size\nOtpInput::new(&otp_state).with_size(px(55.))\n```\n\n### Grouped Layout\n\n```rust\n// Single group (all fields together)\nOtpInput::new(&otp_state).groups(1)\n\n// Two groups (default) - splits fields in half\nOtpInput::new(&otp_state).groups(2)\n\n// Three groups - splits fields into thirds\nOtpInput::new(&otp_state).groups(3)\n```\n\n### Disabled State\n\n```rust\nOtpInput::new(&otp_state).disabled(true)\n```\n\n### Different Length Codes\n\n```rust\n// 4-digit PIN\nlet pin_state = cx.new(|cx| OtpState::new(4, window, cx));\nOtpInput::new(&pin_state).groups(1)\n\n// 6-digit SMS code (most common)\nlet sms_state = cx.new(|cx| OtpState::new(6, window, cx));\nOtpInput::new(&sms_state)\n\n// 8-digit authenticator code\nlet auth_state = cx.new(|cx| OtpState::new(8, window, cx));\nOtpInput::new(&auth_state).groups(2)\n```\n\n### Handle OTP Events\n\n```rust\nlet otp_state = cx.new(|cx| OtpState::new(6, window, cx));\n\ncx.subscribe(&otp_state, |this, state, event: &InputEvent, cx| {\n    match event {\n        InputEvent::Change => {\n            let code = state.read(cx).value();\n            if code.len() == 6 {\n                println!(\"Complete OTP: {}\", code);\n                // Automatically submit when complete\n                this.verify_otp(&code, cx);\n            }\n        }\n        InputEvent::Focus => println!(\"OTP input focused\"),\n        InputEvent::Blur => println!(\"OTP input lost focus\"),\n        _ => {}\n    }\n});\n```\n\n### Programmatic Control\n\n```rust\n// Set value programmatically\notp_state.update(cx, |state, cx| {\n    state.set_value(\"123456\", window, cx);\n});\n\n// Toggle masking\notp_state.update(cx, |state, cx| {\n    state.set_masked(true, window, cx);\n});\n\n// Focus the input\notp_state.update(cx, |state, cx| {\n    state.focus(window, cx);\n});\n\n// Get current value\nlet current_value = otp_state.read(cx).value();\n```\n\n## API Reference\n\n### OtpState\n\n| Method                         | Description                                  |\n| ------------------------------ | -------------------------------------------- |\n| `new(length, window, cx)`      | Create a new OTP state with specified length |\n| `default_value(str)`           | Set initial value                            |\n| `masked(bool)`                 | Enable masked display (shows asterisks)      |\n| `set_value(str, window, cx)`   | Set OTP value programmatically               |\n| `value()`                      | Get current OTP value                        |\n| `set_masked(bool, window, cx)` | Toggle masked display                        |\n| `focus(window, cx)`            | Focus the OTP input                          |\n| `focus_handle(cx)`             | Get focus handle                             |\n\n### OtpInput\n\n| Method           | Description                              |\n| ---------------- | ---------------------------------------- |\n| `new(state)`     | Create OTP input with state entity       |\n| `groups(n)`      | Set number of visual groups (default: 2) |\n| `disabled(bool)` | Set disabled state                       |\n| `small()`        | Small size (6x6 px fields)               |\n| `large()`        | Large size (11x11 px fields)             |\n| `with_size(px)`  | Custom field size                        |\n\n### InputEvent\n\n| Event    | Description                                       |\n| -------- | ------------------------------------------------- |\n| `Change` | Emitted when OTP is complete (all digits entered) |\n| `Focus`  | Input received focus                              |\n| `Blur`   | Input lost focus                                  |\n\n## Examples\n\n### SMS Verification\n\n```rust\nstruct SmsVerification {\n    otp_state: Entity<OtpState>,\n    phone_number: String,\n    is_verifying: bool,\n}\n\nimpl SmsVerification {\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let otp_state = cx.new(|cx| OtpState::new(6, window, cx));\n\n        cx.subscribe(&otp_state, |this, state, event: &InputEvent, cx| {\n            if let InputEvent::Change = event {\n                let code = state.read(cx).value();\n                this.verify_sms_code(&code, cx);\n            }\n        });\n\n        Self {\n            otp_state,\n            phone_number: \"+1234567890\".to_string(),\n            is_verifying: false,\n        }\n    }\n\n    fn verify_sms_code(&mut self, code: &str, cx: &mut Context<Self>) {\n        self.is_verifying = true;\n        // API call to verify SMS code\n        println!(\"Verifying SMS code: {}\", code);\n        cx.notify();\n    }\n}\n\nimpl Render for SmsVerification {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_4()\n            .child(format!(\"Enter the 6-digit code sent to {}\", self.phone_number))\n            .child(OtpInput::new(&self.otp_state))\n            .when(self.is_verifying, |this| {\n                this.child(\"Verifying...\")\n            })\n    }\n}\n```\n\n### Two-Factor Authentication\n\n```rust\nstruct TwoFactorAuth {\n    otp_state: Entity<OtpState>,\n    is_masked: bool,\n}\n\nimpl TwoFactorAuth {\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let otp_state = cx.new(|cx|\n            OtpState::new(6, window, cx)\n                .masked(true)\n        );\n\n        Self {\n            otp_state,\n            is_masked: true,\n        }\n    }\n\n    fn toggle_visibility(&mut self, window: &mut Window, cx: &mut Context<Self>) {\n        self.is_masked = !self.is_masked;\n        self.otp_state.update(cx, |state, cx| {\n            state.set_masked(self.is_masked, window, cx);\n        });\n    }\n}\n\nimpl Render for TwoFactorAuth {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_4()\n            .child(\"Enter your authenticator code\")\n            .child(OtpInput::new(&self.otp_state))\n            .child(\n                Button::new(\"toggle-visibility\")\n                    .label(if self.is_masked { \"Show\" } else { \"Hide\" })\n                    .on_click(cx.listener(Self::toggle_visibility))\n            )\n    }\n}\n```\n\n### PIN Entry\n\n```rust\nstruct PinEntry {\n    pin_state: Entity<OtpState>,\n    attempts: usize,\n    max_attempts: usize,\n}\n\nimpl PinEntry {\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let pin_state = cx.new(|cx|\n            OtpState::new(4, window, cx)\n                .masked(true)\n        );\n\n        cx.subscribe(&pin_state, |this, state, event: &InputEvent, cx| {\n            if let InputEvent::Change = event {\n                let pin = state.read(cx).value();\n                this.verify_pin(&pin, cx);\n            }\n        });\n\n        Self {\n            pin_state,\n            attempts: 0,\n            max_attempts: 3,\n        }\n    }\n\n    fn verify_pin(&mut self, pin: &str, cx: &mut Context<Self>) {\n        self.attempts += 1;\n\n        // Simulate PIN verification\n        if pin == \"1234\" {\n            println!(\"PIN verified successfully!\");\n        } else {\n            println!(\"Incorrect PIN. Attempts: {}/{}\", self.attempts, self.max_attempts);\n\n            // Clear PIN on incorrect attempt\n            self.pin_state.update(cx, |state, cx| {\n                state.set_value(\"\", window, cx);\n            });\n        }\n\n        cx.notify();\n    }\n}\n\nimpl Render for PinEntry {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let is_locked = self.attempts >= self.max_attempts;\n\n        v_flex()\n            .gap_4()\n            .child(\"Enter your 4-digit PIN\")\n            .child(\n                OtpInput::new(&self.pin_state)\n                    .groups(1)\n                    .disabled(is_locked)\n            )\n            .when(is_locked, |this| {\n                this.child(\"Too many attempts. Please try again later.\")\n            })\n            .when(self.attempts > 0 && !is_locked, |this| {\n                this.child(format!(\n                    \"Incorrect PIN. {} attempts remaining.\",\n                    self.max_attempts - self.attempts\n                ))\n            })\n    }\n}\n```\n\n## Behavior\n\n### Input Handling\n\n- **Numeric Only**: Accepts only digits (0-9)\n- **Auto-Focus**: Automatically moves to next field when digit is entered\n- **Backspace**: Removes current digit and moves to previous field\n- **Length Limit**: Prevents input beyond specified length\n- **Auto-Complete**: Emits `Change` event when all fields are filled\n\n### Visual Feedback\n\n- **Focus Indicator**: Blue border and blinking cursor on active field\n- **Masking**: Shows asterisk icons instead of numbers when enabled\n- **Grouping**: Visual separation of fields into groups for better readability\n- **Disabled State**: Grayed out appearance when disabled\n\n### Keyboard Navigation\n\n- **Arrow Keys**: Navigate between fields\n- **Tab**: Move to next focusable element\n- **Shift+Tab**: Move to previous focusable element\n- **Backspace**: Delete current digit and move backward\n- **Delete**: Clear current field\n\n## Common Patterns\n\n### Auto-Submit on Complete\n\n```rust\ncx.subscribe(&otp_state, |this, state, event: &InputEvent, cx| {\n    if let InputEvent::Change = event {\n        let code = state.read(cx).value();\n        if code.len() == 6 {\n            // Auto-submit when complete\n            this.submit_verification_code(&code, cx);\n        }\n    }\n});\n```\n\n### Clear on Focus\n\n```rust\ncx.subscribe(&otp_state, |this, state, event: &InputEvent, cx| {\n    if let InputEvent::Focus = event {\n        // Clear previous value when user starts entering new code\n        state.update(cx, |state, cx| {\n            state.set_value(\"\", window, cx);\n        });\n    }\n});\n```\n\n### Resend Code Timer\n\n```rust\nstruct OtpWithResend {\n    otp_state: Entity<OtpState>,\n    resend_timer: Option<Timer>,\n    can_resend: bool,\n}\n\n// Implementation would include timer logic for resend functionality\n```\n"
  },
  {
    "path": "docs/docs/components/pagination.md",
    "content": "---\ntitle: Pagination\ndescription: Pagination with page navigation, next and previous links.\n---\n\n# Pagination\n\nThe [Pagination] component provides page navigation with next and previous links. It displays page numbers and allows users to navigate through multiple pages of content.\n\n## Import\n\n```rust\nuse gpui_component::pagination::Pagination;\n```\n\n## Usage\n\n### Basic Pagination\n\n```rust\nPagination::new(\"my-pagination\")\n    .current_page(5)\n    .total_pages(10)\n    .on_click(|page, _, cx| {\n        println!(\"Navigated to page: {}\", page);\n    })\n```\n\n### With Visible Pages\n\nBy default, the pagination shows up to 5 visible page buttons. You can customize this with `visible_pages()`:\n\n```rust\nPagination::new(\"my-pagination\")\n    .current_page(1)\n    .total_pages(50)\n    .visible_pages(10)\n    .on_click(|page, _, cx| {\n        // Handle page change\n    })\n```\n\n### Compact Style\n\nThe compact style only shows the previous and next buttons with icons, without displaying page numbers.\n\nUse `compact` method to enable compact style:\n\n```rust\nPagination::new(\"my-pagination\")\n    .compact()\n    .current_page(3)\n    .total_pages(10)\n    .on_click(|page, _, cx| {\n        // Handle page change\n    })\n```\n\n### Different Sizes\n\nThe Pagination supports the [Sizable] trait for different sizes:\n\n```rust\nuse gpui_component::{Sizable as _, Size};\n\nPagination::new(\"my-pagination\")\n    .xsmall()\n    .current_page(1)\n    .total_pages(10)\n\nPagination::new(\"my-pagination\")\n    .small()\n    .current_page(1)\n    .total_pages(10)\n\nPagination::new(\"my-pagination\")\n    .current_page(1)\n    .total_pages(10) // Medium (default)\n\nPagination::new(\"my-pagination\")\n    .large()\n    .current_page(1)\n    .total_pages(10)\n```\n\n### Disabled State\n\n```rust\nPagination::new(\"my-pagination\")\n    .current_page(4)\n    .total_pages(10)\n    .disabled(true)\n    .on_click(|_, _, _| {})\n```\n\n### Handle Page Change Events\n\nThe `on_click` callback receives the new page number when users click on page numbers, previous, or next buttons:\n\n```rust\nPagination::new(\"my-pagination\")\n    .current_page(current_page)\n    .total_pages(total_pages)\n    .on_click(|page, _, cx| {\n        // Update your state with the new page\n        // The page number is 1-based\n    })\n```\n\n## API Reference\n\n- [Pagination]\n\n### Sizing\n\nImplements [Sizable] trait:\n\n- `xsmall()` - Extra small size\n- `small()` - Small size\n- `medium()` - Medium size (default)\n- `large()` - Large size\n- `with_size(size)` - Set custom size\n\n### Methods\n\n- `current_page(page: usize)` - Set the current page number (1-based). The value will be clamped between 1 and total_pages.\n- `total_pages(pages: usize)` - Set the total number of pages.\n- `visible_pages(max: usize)` - Set the maximum number of visible page buttons (default: 5).\n- `compact()` - Enable compact style (only shows prev/next buttons with icons).\n- `disabled(bool)` - Set the disabled state.\n- `on_click(handler)` - Set the handler for page change events.\n\n## Examples\n\n### With State Management\n\n```rust\nlet mut current_page = 1;\nlet total_pages = 20;\n\nPagination::new(\"pagination\")\n    .current_page(current_page)\n    .total_pages(total_pages)\n    .on_click({\n        let entity = entity.clone();\n        move |page, _, cx| {\n            entity.update(cx, |this, cx| {\n                this.current_page = *page;\n                cx.notify();\n            });\n        }\n    })\n```\n\n### Large Dataset Pagination\n\nFor large datasets, use `visible_pages()` to show more page options:\n\n```rust\nPagination::new(\"large-pagination\")\n    .current_page(25)\n    .total_pages(100)\n    .visible_pages(10)\n    .on_click(|page, _, cx| {\n        // Load data for the new page\n    })\n```\n\n[Pagination]: https://docs.rs/gpui-component/latest/gpui_component/pagination/struct.Pagination.html\n[Sizable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Sizable.html\n"
  },
  {
    "path": "docs/docs/components/plot.md",
    "content": "---\ntitle: Plot\ndescription: A low-level plotting library for creating custom charts and data visualizations.\n---\n\n# Plot\n\nThe `plot` module provides low-level building blocks for creating custom charts. It includes scales, shapes, and utilities that power the high-level `Chart` components.\n\n## Import\n\n```rust\nuse gpui_component::plot::{\n    scale::{Scale, ScaleLinear, ScaleBand, ScalePoint, ScaleOrdinal},\n    shape::{Bar, Stack, Line, Area, Pie, Arc},\n    PlotAxis, AxisText\n};\n```\n\n## Scales\n\nScales map a dimension of abstract data to a visual representation.\n\n### ScaleLinear\n\nMaps a continuous quantitative domain to a continuous range.\n\n```rust\nlet scale = ScaleLinear::new(\n    vec![0., 100.],   // Domain (data values)\n    vec![0., 500.]    // Range (pixel coordinates)\n);\n\nscale.tick(&50.); // Returns pixel position\n```\n\n### ScaleBand\n\nMaps a discrete domain to a continuous range, useful for bar charts.\n\n```rust\nlet scale = ScaleBand::new(\n    vec![\"A\", \"B\", \"C\"], // Domain\n    vec![0., 300.]       // Range\n)\n.padding_inner(0.1)\n.padding_outer(0.1);\n\nscale.band_width(); // Returns width of each band\nscale.tick(&\"A\");   // Returns start position of band \"A\"\n```\n\n### ScalePoint\n\nMaps a discrete domain to a set of points in a continuous range, useful for scatter plots or line charts with categorical axes.\n\n```rust\nlet scale = ScalePoint::new(\n    vec![\"A\", \"B\", \"C\"], // Domain\n    vec![0., 300.]       // Range\n);\n\nscale.tick(&\"A\"); // Returns position of point \"A\"\n```\n\n### ScaleOrdinal\n\nMaps a discrete domain to a discrete range.\n\n```rust\nlet scale = ScaleOrdinal::new(\n    vec![\"A\", \"B\", \"C\"], // Domain\n    vec![color1, color2, color3] // Range\n);\n\nscale.map(&\"A\"); // Returns color1\n```\n\n## Shapes\n\n### Bar\n\nRenders a bar shape, commonly used in bar charts.\n\n```rust\nBar::new()\n    .data(data)\n    .band_width(30.)\n    .x(|d| x_scale.tick(&d.category))\n    .y0(|d| y_scale.tick(&0.).unwrap())\n    .y1(|d| y_scale.tick(&d.value))\n    .fill(|d| color_scale.map(&d.category))\n    .paint(&bounds, window, cx);\n```\n\n### Line\n\nRenders a line shape, commonly used in line charts.\n\n```rust\nLine::new()\n    .data(data)\n    .x(|d| x_scale.tick(&d.date))\n    .y(|d| y_scale.tick(&d.value))\n    .stroke(cx.theme().chart_1)\n    .stroke_width(px(2.))\n    .paint(&bounds, window);\n```\n\n#### Line with Dots\n\nSupports rendering dots at data points.\n\n```rust\nLine::new()\n    .data(data)\n    .x(|d| x_scale.tick(&d.date))\n    .y(|d| y_scale.tick(&d.value))\n    .dot()\n    .dot_size(px(4.))\n    .paint(&bounds, window);\n```\n\n### Area\n\nRenders an area shape, commonly used in area charts.\n\n```rust\nArea::new()\n    .data(data)\n    .x(|d| x_scale.tick(&d.date))\n    .y0(height) // Baseline\n    .y1(|d| y_scale.tick(&d.value))\n    .fill(cx.theme().chart_1.opacity(0.5))\n    .stroke(cx.theme().chart_1)\n    .paint(&bounds, window);\n```\n\n### Pie & Arc\n\nRenders pie charts and donut charts using `Pie` layout and `Arc` shape.\n\n```rust\n// 1. Compute pie layout\nlet pie = Pie::new()\n    .value(|d| Some(d.value))\n    .pad_angle(0.05);\n\nlet arcs = pie.arcs(&data);\n\n// 2. Render arcs\nlet arc_shape = Arc::new()\n    .inner_radius(0.)\n    .outer_radius(100.);\n\nfor arc_data in arcs {\n    arc_shape.paint(\n        &arc_data,\n        color_scale.map(&arc_data.data.category), // Color\n        None, // Override inner radius\n        None, // Override outer radius\n        &bounds,\n        window\n    );\n}\n```\n\n### Stack\n\nComputes stacked layout for data series.\n\n```rust\nlet stack = Stack::new()\n    .data(data)\n    .keys(vec![\"series1\", \"series2\"])\n    .value(|d, key| match key {\n        \"series1\" => Some(d.val1),\n        \"series2\" => Some(d.val2),\n        _ => None\n    });\n\nlet series = stack.series(); // Returns Vec<StackSeries<T>>\n```\n\n## Components\n\n### PlotAxis\n\nRenders chart axes with labels and ticks.\n\n```rust\nPlotAxis::new()\n    .x(height) // Y position for X axis\n    .x_label(labels) // Iterator of AxisText\n    .stroke(cx.theme().border)\n    .paint(&bounds, window, cx);\n```\n\n## Examples\n\n### Custom Bar Chart Implementation\n\nHere's how to implement a custom stacked bar chart using low-level plot primitives:\n\n```rust\nstruct StackedBarChart {\n    data: Vec<DailyDevice>,\n    series: Vec<StackSeries<DailyDevice>>,\n}\n\nimpl StackedBarChart {\n    pub fn new(data: Vec<DailyDevice>) -> Self {\n        let series = Stack::new()\n            .data(data.clone())\n            .keys(vec![\"desktop\", \"mobile\"])\n            .value(|d, key| match key {\n                \"desktop\" => Some(d.desktop),\n                \"mobile\" => Some(d.mobile),\n                _ => None,\n            })\n            .series();\n\n        Self { data, series }\n    }\n}\n\nimpl Plot for StackedBarChart {\n    fn paint(&mut self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App) {\n        // 1. Setup Scales\n        let x = ScaleBand::new(\n            self.data.iter().map(|v| v.date.clone()).collect(),\n            vec![0., width],\n        );\n        \n        let y = ScaleLinear::new(vec![0., max_value], vec![height, 0.]);\n\n        // 2. Draw Axis\n        // ... (axis rendering logic)\n\n        // 3. Draw Stacked Bars\n        let bar = Bar::new()\n            .stack_data(&self.series)\n            .band_width(x.band_width())\n            .x(move |d| x.tick(&d.data.date))\n            .fill(move |_| cx.theme().chart_1);\n\n        bar.paint(&bounds, window, cx);\n    }\n}\n```\n"
  },
  {
    "path": "docs/docs/components/popover.md",
    "content": "---\ntitle: Popover\ndescription: A floating overlay that displays rich content relative to a trigger element.\n---\n\n# Popover\n\nPopover component for displaying floating content that appears when interacting with a trigger element. Supports multiple positioning options, custom content, different trigger methods, and automatic dismissal behaviors. Perfect for tooltips, menus, forms, and other contextual information.\n\n## Import\n\n```rust\nuse gpui_component::popover::{Popover};\n```\n\n## Usage\n\n### Basic Popover\n\n:::info\nAny element that implements [Selectable] can be used as a trigger, for example, a [Button].\n\nAny element that implements [RenderOnce] or [Render] can be used as popover content, use `.child(...)` to add children directly.\n:::\n\n```rust\nuse gpui::ParentElement as _;\nuse gpui_component::{button::Button, popover::Popover};\n\nPopover::new(\"basic-popover\")\n    .trigger(Button::new(\"trigger\").label(\"Click me\").outline())\n    .child(\"Hello, this is a popover!\")\n    .child(\"It appears when you click the button.\")\n```\n\n### Popover with Custom Positioning\n\nThe `anchor` method allows you to specify where the popover appears relative to the trigger element. It accepts both `Corner` and `Anchor` types.\n\n**Using `Corner` type** (4 corner positions):\n\n```rust\nuse gpui::Corner;\n\nPopover::new(\"positioned-popover\")\n    .anchor(Corner::TopRight)\n    .trigger(Button::new(\"top-right\").label(\"Top Right\").outline())\n    .child(\"This popover appears at the top right\")\n```\n\n**Using `Anchor` type** (6 positions including center):\n\nThe `Anchor` type provides more positioning options, including center positions:\n\n```rust\nuse gpui_component::Anchor;\n\n// Top positions\nPopover::new(\"top-left\")\n    .anchor(Anchor::TopLeft)\n    .trigger(Button::new(\"btn\").label(\"Top Left\").outline())\n    .child(\"Anchored to top left\")\n\nPopover::new(\"top-center\")\n    .anchor(Anchor::TopCenter)\n    .trigger(Button::new(\"btn\").label(\"Top Center\").outline())\n    .child(\"Anchored to top center\")\n\nPopover::new(\"top-right\")\n    .anchor(Anchor::TopRight)\n    .trigger(Button::new(\"btn\").label(\"Top Right\").outline())\n    .child(\"Anchored to top right\")\n\n// Bottom positions\nPopover::new(\"bottom-left\")\n    .anchor(Anchor::BottomLeft)\n    .trigger(Button::new(\"btn\").label(\"Bottom Left\").outline())\n    .child(\"Anchored to bottom left\")\n\nPopover::new(\"bottom-center\")\n    .anchor(Anchor::BottomCenter)\n    .trigger(Button::new(\"btn\").label(\"Bottom Center\").outline())\n    .child(\"Anchored to bottom center\")\n\nPopover::new(\"bottom-right\")\n    .anchor(Anchor::BottomRight)\n    .trigger(Button::new(\"btn\").label(\"Bottom Right\").outline())\n    .child(\"Anchored to bottom right\")\n```\n\n### View in Popover\n\nYou can add any `Entity<T>` that implemented [Render] as the popover content.\n\n```rust\nlet view = cx.new(|_| MyView::new());\n\nPopover::new(\"form-popover\")\n    .anchor(Corner::BottomLeft)\n    .trigger(Button::new(\"show-form\").label(\"Open Form\").outline())\n    .child(view.clone())\n```\n\n### Add content by `content` method\n\nThe `content` method allows you to create more complex popover content using a closure. This is useful when\nyou need to build dynamic content or need access to the popover's context.\n\nThis method will let us to have `&mut PopoverState`, `&mut Window` and `&mut Context<PopoverState>` parameters in the\nclosure is to allow you to interact with the popover's state and the overall application context if needed.\n\n:::warning\nThis `content` callback will called every time on render the popover.\nSo, you should avoid creating new elements or entities in the content closure\nor other heavy operations that may impact performance.\n:::\n\nAnd `content` will works with `child`, `children` methods together.\n\n```rust\nuse gpui::ParentElement as _;\nuse gpui_component::popover::Popover;\n\nPopover::new(\"complex-popover\")\n    .anchor(Corner::BottomLeft)\n    .trigger(Button::new(\"complex\").label(\"Complex Content\").outline())\n    .content(|_, _, _| {\n        div()\n            .child(\"This popover has complex content.\")\n            .child(\n                Button::new(\"action-btn\")\n                    .label(\"Perform Action\")\n                    .outline()\n            )\n    })\n```\n\n### Right-Click Popover\n\nSometimes you may want to show a popover on right-click, for example, to create a special your ownen context menu. The `mouse_button` method allows you to specify which mouse button triggers the popover.\n\n```rust\nuse gpui::MouseButton;\n\nPopover::new(\"context-menu\")\n    .anchor(Corner::BottomRight)\n    .mouse_button(MouseButton::Right)\n    .trigger(Button::new(\"right-click\").label(\"Right Click Me\").outline())\n    .child(\"Context Menu\")\n    .child(Divider::horizontal())\n    .child(\"This is a custom context menu.\")\n```\n\n### Dismiss Popover manually\n\nIf you want to dismiss the popover programmatically from within the content, you can emit a `DismissEvent`. In this case, you should use `content` method to create the popover content so you have access to the `cx: &mut Context<PopoverState>`.\n\n```rust\nuse gpui_component::{DismissEvent, popover::Popover};\n\nPopover::new(\"dismiss-popover\")\n    .trigger(Button::new(\"dismiss\").label(\"Dismiss Popover\").outline())\n    .content(|_, cx| {\n        div()\n            .child(\"Click the button below to dismiss this popover.\")\n            .child(\n                Button::new(\"close-btn\")\n                    .label(\"Close Popover\")\n                    .on_click(cx.listener(|_, _, _, cx| {\n                        // NOTE: Here `cx` is `&mut Context<PopoverState>` type, so we can emit DismissEvent.\n                        cx.emit(DismissEvent);\n                    }))\n            )\n    })\n```\n\n### Styling Popover\n\nLike the others components in GPUI Component, the `appearance(false)` method can be used to disable the default styling of the popover, allowing you to fully customize its appearance.\n\nAnd the `Popover` has implemented the [Styled] trait, so you can use all the styling methods provided by GPUI to style the popover content as you like.\n\n```rust\n// For custom styled popovers or when you want full control\nPopover::new(\"custom-popover\")\n    .appearance(false)\n    .trigger(Button::new(\"custom\").label(\"Custom Style\"))\n    .bg(cx.theme().accent)\n    .text_color(cx.theme().accent_foreground)\n    .p_6()\n    .rounded_xl()\n    .shadow_2xl()\n    .child(\"Fully custom styled popover\")\n```\n\n### Control Open State\n\nThere have `open` and `on_open_change` methods to control the open state of the popover programmatically.\n\nThis is useful when you want to synchronize the popover's open state with other UI elements or application state.\n\n:::tip\nWhen you use `open` to control the popover's open state, that means you have take full control of it,\nso you need to update the state in `on_open_change` callback to keep the popover working correctly.\n:::\n\n```rust\nuse gpui_component::popover::Popover;\n\nstruct MyView {\n    popover_open: bool,\n}\n\nPopover::new(\"controlled-popover\")\n    .open(self.open)\n    .on_open_change(cx.listener(|this, open: &bool, _, cx| {\n        this.popover_open = *open;\n        cx.notify();\n    }))\n    .trigger(Button::new(\"control-btn\").label(\"Control Popover\").outline())\n    .child(\"This popover's open state is controlled programmatically.\")\n```\n\n### Default Open\n\nThe `default_open` method allows you to set the initial open state of the popover when it is first rendered.\n\nPlease note that if you use the `open` method to control the popover's open state, the `default_open` setting will be ignored.\n\n```rust\nuse gpui_component::popover::Popover;\n\nPopover::new(\"default-open-popover\")\n    .default_open(true)\n    .trigger(Button::new(\"default-open-btn\").label(\"Default Open\").outline())\n    .child(\"This popover is open by default when first rendered.\")\n```\n\n[Button]: https://docs.rs/gpui-component/latest/gpui_component/button/struct.Button.html\n[Selectable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Selectable.html\n[Render]: https://docs.rs/gpui/latest/gpui/trait.Render.html\n[RenderOnce]: https://docs.rs/gpui/latest/gpui/trait.RenderOnce.html\n[Styled]: https://docs.rs/gpui/latest/gpui/trait.Styled.html\n"
  },
  {
    "path": "docs/docs/components/progress.md",
    "content": "---\ntitle: Progress\ndescription: Displays an indicator showing the completion progress of a task, typically displayed as a progress bar or circular indicator.\n---\n\n# Progress\n\nProgress components visually represent the completion percentage of a task. The library provides two variants:\n\n- **[Progress](#progress)** - A linear horizontal progress bar\n- **[ProgressCircle](#progresscircle)** - A circular progress indicator\n\nBoth components feature smooth animations, customizable colors, and automatic styling that adapts to the current theme.\n\n## Progress\n\n```rust\nuse gpui_component::progress::{Progress, ProgressCircle};\n```\n\n### Usage\n\n```rust\nProgress::new(\"my-progress\")\n    .value(50.0) // 50% complete\n```\n\n### Different Progress Values\n\n```rust\n// Starting state (0%)\nProgress::new(\"progress-0\")\n    .value(0.0)\n\n// Partially complete (25%)\nProgress::new(\"progress-25\")\n    .value(25.0)\n\n// Nearly complete (75%)\nProgress::new(\"progress-75\")\n    .value(75.0)\n\n// Complete (100%)\nProgress::new(\"progress-100\")\n    .value(100.0)\n```\n\n### Indeterminate State\n\n```rust\n// For unknown progress duration\nProgress::new(\"indeterminate\")\n    .value(-1.0) // Any negative value shows as 0%\n\n// Or explicitly set to 0 for starting state\nProgress::new(\"starting\")\n    .value(0.0)\n```\n\n### Dynamic Progress Updates\n\n```rust\nstruct MyComponent {\n    progress_value: f32,\n}\n\nimpl MyComponent {\n    fn update_progress(&mut self, new_value: f32) {\n        self.progress_value = new_value.clamp(0.0, 100.0);\n    }\n\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_3()\n            .child(\n                h_flex()\n                    .gap_2()\n                    .child(Button::new(\"decrease\").label(\"-\").on_click(\n                        cx.listener(|this, _, _, _| {\n                            this.update_progress(this.progress_value - 10.0);\n                        })\n                    ))\n                    .child(Button::new(\"increase\").label(\"+\").on_click(\n                        cx.listener(|this, _, _, _| {\n                            this.update_progress(this.progress_value + 10.0);\n                        })\n                    ))\n            )\n            .child(Progress::new(\"progress\").value(self.progress_value))\n            .child(format!(\"{}%\", self.progress_value as i32))\n    }\n}\n```\n\n### File Upload Progress\n\n```rust\nstruct FileUpload {\n    bytes_uploaded: u64,\n    total_bytes: u64,\n}\n\nimpl FileUpload {\n    fn progress_percentage(&self) -> f32 {\n        if self.total_bytes == 0 {\n            0.0\n        } else {\n            (self.bytes_uploaded as f32 / self.total_bytes as f32) * 100.0\n        }\n    }\n\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_2()\n            .child(\"Uploading file...\")\n            .child(Progress::new(\"upload-progress\").value(self.progress_percentage()))\n            .child(format!(\n                \"{} / {} bytes\",\n                self.bytes_uploaded,\n                self.total_bytes\n            ))\n    }\n}\n```\n\n### Loading States\n\n```rust\nstruct LoadingComponent {\n    is_loading: bool,\n    progress: f32,\n}\n\nimpl LoadingComponent {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_3()\n            .when(self.is_loading, |this| {\n                this.child(\"Loading...\")\n                    .child(Progress::new(\"loading-progress\").value(self.progress))\n            })\n            .when(!self.is_loading, |this| {\n                this.child(\"Task completed!\")\n                    .child(Progress::new(\"loading-progress\").value(100.0))\n            })\n    }\n}\n```\n\n### Multi-Step Process\n\n```rust\nenum ProcessStep {\n    Initializing,\n    Processing,\n    Finalizing,\n    Complete,\n}\n\nstruct MultiStepProcess {\n    current_step: ProcessStep,\n    step_progress: f32,\n}\n\nimpl MultiStepProcess {\n    fn overall_progress(&self) -> f32 {\n        let base_progress = match self.current_step {\n            ProcessStep::Initializing => 0.0,\n            ProcessStep::Processing => 33.33,\n            ProcessStep::Finalizing => 66.66,\n            ProcessStep::Complete => 100.0,\n        };\n\n        if matches!(self.current_step, ProcessStep::Complete) {\n            100.0\n        } else {\n            base_progress + (self.step_progress / 3.0)\n        }\n    }\n\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_3()\n            .child(match self.current_step {\n                ProcessStep::Initializing => \"Initializing...\",\n                ProcessStep::Processing => \"Processing data...\",\n                ProcessStep::Finalizing => \"Finalizing...\",\n                ProcessStep::Complete => \"Complete!\",\n            })\n            .child(Progress::new(\"overall-progress\").value(self.overall_progress()))\n            .child(format!(\"{:.1}% complete\", self.overall_progress()))\n    }\n}\n```\n\n## ProgressCircle\n\nA circular progress indicator component that displays progress as an arc around a circle. Perfect for compact spaces, button icons, or when you want a more modern, space-efficient progress display.\n\n```rust\nuse gpui_component::progress::ProgressCircle;\n```\n\n### Basic ProgressCircle\n\n```rust\nProgressCircle::new(\"my-progress-circle\")\n    .value(50.0) // 50% complete\n```\n\n### Different Sizes\n\nProgressCircle supports different sizes through the `Sizable` trait:\n\n```rust\n// Extra small\nProgressCircle::new(\"progress-xs\")\n    .value(25.0)\n    .xsmall()\n\n// Small\nProgressCircle::new(\"progress-sm\")\n    .value(50.0)\n    .small()\n\n// Medium (default)\nProgressCircle::new(\"progress-md\")\n    .value(75.0)\n    .medium()\n\n// Large\nProgressCircle::new(\"progress-lg\")\n    .value(100.0)\n    .large()\n\n// Custom size\nProgressCircle::new(\"progress-custom\")\n    .value(60.0)\n    .size(px(80.))\n```\n\n### Custom Colors\n\n```rust\n// Use theme colors (default)\nProgressCircle::new(\"progress-default\")\n    .value(50.0)\n\n// Custom color\nProgressCircle::new(\"progress-green\")\n    .value(75.0)\n    .color(cx.theme().green)\n\n// Different color variants\nProgressCircle::new(\"progress-blue\")\n    .value(60.0)\n    .color(cx.theme().blue)\n\nProgressCircle::new(\"progress-yellow\")\n    .value(40.0)\n    .color(cx.theme().yellow)\n\nProgressCircle::new(\"progress-red\")\n    .value(80.0)\n    .color(cx.theme().red)\n```\n\n### With Labels\n\n```rust\nh_flex()\n    .gap_2()\n    .items_center()\n    .child(\n        ProgressCircle::new(\"download-progress\")\n            .value(65.0)\n            .size_4()\n    )\n    .child(\"Downloading... 65%\")\n```\n\n## Examples\n\n### Task Progress with Status\n\n```rust\nstruct TaskProgress {\n    completed_tasks: usize,\n    total_tasks: usize,\n}\n\nimpl TaskProgress {\n    fn progress_value(&self) -> f32 {\n        if self.total_tasks == 0 {\n            0.0\n        } else {\n            (self.completed_tasks as f32 / self.total_tasks as f32) * 100.0\n        }\n    }\n\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_2()\n            .child(\n                h_flex()\n                    .justify_between()\n                    .child(\"Task Progress\")\n                    .child(format!(\"{}/{}\", self.completed_tasks, self.total_tasks))\n            )\n            .child(Progress::new(\"task-progress\").value(self.progress_value()))\n            .when(self.completed_tasks == self.total_tasks, |this| {\n                this.child(\"All tasks completed!\")\n            })\n    }\n}\n```\n\n### Download Progress with Speed\n\n```rust\nstruct DownloadProgress {\n    downloaded: u64,\n    total_size: u64,\n    speed_mbps: f32,\n}\n\nimpl DownloadProgress {\n    fn eta_seconds(&self) -> u64 {\n        if self.speed_mbps == 0.0 {\n            0\n        } else {\n            let remaining_mb = (self.total_size - self.downloaded) as f32 / 1_000_000.0;\n            (remaining_mb / self.speed_mbps) as u64\n        }\n    }\n\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        let progress = (self.downloaded as f32 / self.total_size as f32) * 100.0;\n\n        v_flex()\n            .gap_2()\n            .child(\n                h_flex()\n                    .justify_between()\n                    .child(\"Downloading...\")\n                    .child(format!(\"{:.1}%\", progress))\n            )\n            .child(Progress::new(\"download-progress\").value(progress))\n            .child(\n                h_flex()\n                    .justify_between()\n                    .child(format!(\"{:.1} MB/s\", self.speed_mbps))\n                    .child(format!(\"ETA: {}s\", self.eta_seconds()))\n            )\n    }\n}\n```\n\n### Installation Progress\n\n```rust\nstruct InstallationProgress {\n    current_package: String,\n    package_index: usize,\n    total_packages: usize,\n    package_progress: f32,\n}\n\nimpl InstallationProgress {\n    fn overall_progress(&self) -> f32 {\n        if self.total_packages == 0 {\n            0.0\n        } else {\n            let completed_packages = self.package_index as f32;\n            let current_package_contribution = self.package_progress / 100.0;\n            let total_progress = (completed_packages + current_package_contribution)\n                / self.total_packages as f32;\n            total_progress * 100.0\n        }\n    }\n\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_3()\n            .child(\"Installing packages...\")\n            .child(\n                v_flex()\n                    .gap_2()\n                    .child(\n                        h_flex()\n                            .justify_between()\n                            .child(format!(\"Overall Progress\"))\n                            .child(format!(\"{}/{}\", self.package_index + 1, self.total_packages))\n                    )\n                    .child(Progress::new(\"overall-progress\").value(self.overall_progress()))\n            )\n            .child(\n                v_flex()\n                    .gap_2()\n                    .child(format!(\"Installing: {}\", self.current_package))\n                    .child(Progress::new(\"package-progress\").value(self.package_progress))\n            )\n    }\n}\n```\n\n## Styling and Theming\n\nThe Progress component automatically adapts to the current theme:\n\n### Theme Colors\n\n```rust\n// The progress bar uses theme colors automatically\n// Background: theme.progress_bar with 20% opacity\n// Fill: theme.progress_bar at full opacity\n\n// These colors adapt to light/dark theme automatically\nProgress::new(\"themed-progress\").value(75.0) // Uses theme colors\n```\n"
  },
  {
    "path": "docs/docs/components/radio.md",
    "content": "---\ntitle: Radio\ndescription: A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.\n---\n\n# Radio\n\nRadio buttons allow users to select a single option from a set of mutually exclusive choices. Use radio buttons when you want to give users a choice between multiple options and only one selection is allowed.\n\n## Import\n\n```rust\nuse gpui_component::radio::{Radio, RadioGroup};\n```\n\n## Usage\n\n### Basic Radio Button\n\n```rust\nRadio::new(\"radio-option-1\")\n    .label(\"Option 1\")\n    .checked(false)\n    .on_click(|checked, _, _| {\n        println!(\"Radio is now: {}\", checked);\n    })\n```\n\n### Controlled Radio Button\n\n```rust\nstruct MyView {\n    radio_checked: bool,\n}\n\nimpl Render for MyView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        Radio::new(\"radio\")\n            .label(\"Select this option\")\n            .checked(self.radio_checked)\n            .on_click(cx.listener(|view, checked, _, cx| {\n                view.radio_checked = *checked;\n                cx.notify();\n            }))\n    }\n}\n```\n\n### Radio Group (Recommended)\n\n```rust\nstruct MyView {\n    selected_option: Option<usize>,\n}\n\nimpl Render for MyView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        RadioGroup::horizontal(\"options\")\n            .children([\"Option 1\", \"Option 2\", \"Option 3\"])\n            .selected_index(self.selected_option)\n            .on_change(cx.listener(|view, selected_index: &usize, _, cx| {\n                view.selected_option = Some(*selected_index);\n                cx.notify();\n            }))\n    }\n}\n```\n\n### Different Sizes\n\n```rust\nRadio::new(\"small\").label(\"Small\").xsmall()\nRadio::new(\"medium\").label(\"Medium\") // default\nRadio::new(\"large\").label(\"Large\").large()\n```\n\n### Disabled State\n\n```rust\nRadio::new(\"disabled\")\n    .label(\"Disabled option\")\n    .disabled(true)\n    .checked(false)\n\nRadio::new(\"disabled-checked\")\n    .label(\"Disabled and checked\")\n    .checked(true)\n    .disabled(true)\n```\n\n### Multi-line Label with Custom Content\n\n```rust\nRadio::new(\"custom\")\n    .label(\"Primary option\")\n    .child(\n        div()\n            .text_color(cx.theme().muted_foreground)\n            .child(\"This is additional descriptive text that provides more context.\")\n    )\n    .w(px(300.))\n```\n\n### Custom Tab Order\n\n```rust\nRadio::new(\"radio\")\n    .label(\"Custom tab order\")\n    .tab_index(2)\n    .tab_stop(true)\n```\n\n## Radio Group Usage\n\n### Horizontal Layout\n\n```rust\nRadioGroup::horizontal(\"horizontal-group\")\n    .children([\"First\", \"Second\", \"Third\"])\n    .selected_index(Some(0))\n    .on_change(cx.listener(|view, index, _, cx| {\n        println!(\"Selected index: {}\", index);\n        cx.notify();\n    }))\n```\n\n### Vertical Layout\n\n```rust\nRadioGroup::vertical(\"vertical-group\")\n    .child(Radio::new(\"option1\").label(\"United States\"))\n    .child(Radio::new(\"option2\").label(\"Canada\"))\n    .child(Radio::new(\"option3\").label(\"Mexico\"))\n    .selected_index(Some(1))\n    .disabled(false)\n```\n\n### Styled Radio Group\n\n```rust\nRadioGroup::vertical(\"styled-group\")\n    .w(px(220.))\n    .p_2()\n    .border_1()\n    .border_color(cx.theme().border)\n    .rounded(cx.theme().radius)\n    .child(Radio::new(\"option1\").label(\"Option 1\"))\n    .child(Radio::new(\"option2\").label(\"Option 2\"))\n    .child(Radio::new(\"option3\").label(\"Option 3\"))\n    .selected_index(Some(0))\n```\n\n### Disabled Radio Group\n\n```rust\nRadioGroup::vertical(\"disabled-group\")\n    .children([\"Option A\", \"Option B\", \"Option C\"])\n    .selected_index(Some(1))\n    .disabled(true) // Disables all radio buttons in the group\n```\n\n## API Reference\n\n### Radio\n\n| Method             | Description                                                 |\n| ------------------ | ----------------------------------------------------------- |\n| `new(id)`          | Create a new radio button with the given ID                 |\n| `label(text)`      | Set label text                                              |\n| `checked(bool)`    | Set checked state                                           |\n| `disabled(bool)`   | Set disabled state                                          |\n| `on_click(fn)`     | Callback when clicked, receives `&bool` (new checked state) |\n| `tab_stop(bool)`   | Enable/disable tab navigation (default: true)               |\n| `tab_index(isize)` | Set tab order index (default: 0)                            |\n\n### RadioGroup\n\n| Method                          | Description                                                         |\n| ------------------------------- | ------------------------------------------------------------------- |\n| `horizontal(id)`                | Create a new horizontal radio group                                 |\n| `vertical(id)`                  | Create a new vertical radio group                                   |\n| `layout(Axis)`                  | Set layout direction (Vertical or Horizontal)                       |\n| `child(Radio)`                  | Add a single radio button to the group                              |\n| `children(items)`               | Add multiple radio buttons from an iterator                         |\n| `selected_index(Option<usize>)` | Set the selected option by index                                    |\n| `disabled(bool)`                | Disable all radio buttons in the group                              |\n| `on_change(fn)`                 | Callback when selection changes, receives `&usize` (selected index) |\n\n### Styling\n\nBoth Radio and RadioGroup implement `Styled` trait for custom styling:\n\nRadio also implements `Sizable` trait:\n\n- `xsmall()` - Extra small size\n- `small()` - Small size\n- `medium()` - Medium size (default)\n- `large()` - Large size\n\n## Examples\n\n### Settings Panel\n\n```rust\nstruct SettingsView {\n    theme: Option<usize>, // 0: Light, 1: Dark, 2: Auto\n    language: Option<usize>, // 0: English, 1: Spanish, 2: French\n}\n\nimpl Render for SettingsView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_6()\n            .child(\n                v_flex()\n                    .gap_2()\n                    .child(div().text_sm().font_semibold().child(\"Theme\"))\n                    .child(\n                        RadioGroup::vertical(\"theme\")\n                            .child(Radio::new(\"light\").label(\"Light\"))\n                            .child(Radio::new(\"dark\").label(\"Dark\"))\n                            .child(Radio::new(\"auto\").label(\"Auto\"))\n                            .selected_index(self.theme)\n                            .on_change(cx.listener(|view, index, _, cx| {\n                                view.theme = Some(*index);\n                                cx.notify();\n                            }))\n                    )\n            )\n            .child(\n                v_flex()\n                    .gap_2()\n                    .child(div().text_sm().font_semibold().child(\"Language\"))\n                    .child(\n                        RadioGroup::horizontal(\"language\")\n                            .children([\"English\", \"Español\", \"Français\"])\n                            .selected_index(self.language)\n                            .on_change(cx.listener(|view, index, _, cx| {\n                                view.language = Some(*index);\n                                cx.notify();\n                            }))\n                    )\n            )\n    }\n}\n```\n\n### Survey Form\n\n```rust\nstruct SurveyView {\n    satisfaction: Option<usize>,\n    recommendation: Option<usize>,\n}\n\nimpl Render for SurveyView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_8()\n            .child(\n                v_flex()\n                    .gap_3()\n                    .child(\n                        div()\n                            .text_base()\n                            .font_medium()\n                            .child(\"How satisfied are you with our service?\")\n                    )\n                    .child(\n                        RadioGroup::vertical(\"satisfaction\")\n                            .child(Radio::new(\"very-satisfied\").label(\"Very satisfied\"))\n                            .child(Radio::new(\"satisfied\").label(\"Satisfied\"))\n                            .child(Radio::new(\"neutral\").label(\"Neutral\"))\n                            .child(Radio::new(\"dissatisfied\").label(\"Dissatisfied\"))\n                            .child(Radio::new(\"very-dissatisfied\").label(\"Very dissatisfied\"))\n                            .selected_index(self.satisfaction)\n                            .on_change(cx.listener(|view, index, _, cx| {\n                                view.satisfaction = Some(*index);\n                                cx.notify();\n                            }))\n                    )\n            )\n            .child(\n                v_flex()\n                    .gap_3()\n                    .child(\n                        div()\n                            .text_base()\n                            .font_medium()\n                            .child(\"How likely are you to recommend us?\")\n                    )\n                    .child(\n                        RadioGroup::horizontal(\"recommendation\")\n                            .children((0..=10).map(|i| i.to_string()))\n                            .selected_index(self.recommendation)\n                            .on_change(cx.listener(|view, index, _, cx| {\n                                view.recommendation = Some(*index);\n                                cx.notify();\n                            }))\n                    )\n            )\n    }\n}\n```\n\n### Payment Method Selection\n\n```rust\nstruct PaymentView {\n    payment_method: Option<usize>,\n}\n\nimpl Render for PaymentView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_4()\n            .child(\n                div()\n                    .text_lg()\n                    .font_semibold()\n                    .child(\"Select Payment Method\")\n            )\n            .child(\n                RadioGroup::vertical(\"payment\")\n                    .child(\n                        Radio::new(\"credit-card\")\n                            .label(\"Credit Card\")\n                            .child(\n                                div()\n                                    .text_color(cx.theme().muted_foreground)\n                                    .child(\"Visa, MasterCard, American Express\")\n                            )\n                    )\n                    .child(\n                        Radio::new(\"paypal\")\n                            .label(\"PayPal\")\n                            .child(\n                                div()\n                                    .text_color(cx.theme().muted_foreground)\n                                    .child(\"Pay with your PayPal account\")\n                            )\n                    )\n                    .child(\n                        Radio::new(\"bank-transfer\")\n                            .label(\"Bank Transfer\")\n                            .child(\n                                div()\n                                    .text_color(cx.theme().muted_foreground)\n                                    .child(\"Direct bank account transfer\")\n                            )\n                    )\n                    .selected_index(self.payment_method)\n                    .on_change(cx.listener(|view, index, _, cx| {\n                        view.payment_method = Some(*index);\n                        cx.notify();\n                    }))\n            )\n    }\n}\n```\n\n## Best Practices\n\n1. **Use RadioGroup**: Always prefer `RadioGroup` over individual `Radio` components for mutually exclusive choices\n2. **Clear Labels**: Provide descriptive labels that clearly indicate what each option represents\n3. **Default Selection**: Consider providing a sensible default selection, especially for required fields\n4. **Logical Order**: Arrange options in a logical order (alphabetical, frequency of use, or importance)\n5. **Limit Options**: Keep the number of radio options reasonable (typically 2-7 options)\n6. **Group Related Options**: Use visual grouping and clear headings for multiple radio groups\n7. **Responsive Design**: Consider using horizontal layout for fewer options and vertical for more options\n"
  },
  {
    "path": "docs/docs/components/rating.md",
    "content": "---\ntitle: Rating\ndescription: A simple interactive star rating component.\n---\n\n# Rating\n\nA star rating component that allows users to select a rating value. Supports different sizes, custom colors, disabled state, and click handlers.\n\n## Import\n\n```rust\nuse gpui_component::rating::Rating;\n```\n\n## Usage\n\n### Basic Rating\n\n```rust\nRating::new(\"my-rating\")\n    .value(3)\n    .max(5)\n    .on_click(|value, _, _| {\n        println!(\"Rating changed to: {}\", value);\n    })\n```\n\n### Controlled Rating\n\n```rust\nstruct MyView {\n    rating: usize,\n}\n\nimpl Render for MyView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        Rating::new(\"rating\")\n            .value(self.rating)\n            .max(5)\n            .on_click(cx.listener(|view, value: &usize, _, cx| {\n                view.rating = *value;\n                cx.notify();\n            }))\n    }\n}\n```\n\n### Different Sizes\n\nThe Rating component supports the [Sizable] trait for different sizes.\n\n```rust\nRating::new(\"rating\").xsmall().value(3).max(5)\nRating::new(\"rating\").small().value(3).max(5)\nRating::new(\"rating\").value(3).max(5) // default (Medium)\nRating::new(\"rating\").large().value(3).max(5)\n```\n\n### Custom Color\n\nBy default, the rating uses the theme's `yellow` color. You can customize it with the `color` method.\n\n```rust\nRating::new(\"rating\")\n    .value(4)\n    .max(5)\n    .color(cx.theme().green)\n```\n\n### Disabled State\n\n```rust\nRating::new(\"rating\")\n    .value(2)\n    .max(5)\n    .disabled(true)\n```\n\n### Custom Maximum\n\nThe default maximum is 5 stars, but you can set a different maximum value.\n\n```rust\nRating::new(\"rating\")\n    .value(7)\n    .max(10)\n```\n\n### Click Behavior\n\nThe rating component has special click behavior:\n\n- Clicking on a star that's already filled will reduce the rating by 1\n- Clicking on an unfilled star will set the rating to that star's value\n\nThe `on_click` callback receives the new rating value as `&usize`.\n\n```rust\nRating::new(\"rating\")\n    .value(3)\n    .max(5)\n    .on_click(|new_value, _, _| {\n        println!(\"New rating: {}\", new_value);\n    })\n```\n\n## API Reference\n\n- [Rating]\n\n### Methods\n\n- `new(id: impl Into<ElementId>)` - Create a new Rating component\n- `with_size(size: impl Into<Size>)` - Set the star size (implements [Sizable])\n- `value(value: usize)` - Set the initial rating value (0..=max)\n- `max(max: usize)` - Set the maximum number of stars (default: 5)\n- `color(color: impl Into<Hsla>)` - Set the active color (default: theme yellow)\n- `disabled(disabled: bool)` - Disable interaction (implements [Disableable])\n- `on_click(handler: Fn(&usize, &mut Window, &mut App))` - Set click handler\n\n## Examples\n\n### Read-only Display\n\n```rust\nRating::new(\"rating\")\n    .value(4)\n    .max(5)\n    .disabled(true)\n```\n\n### Interactive Rating with State\n\n```rust\nstruct ProductView {\n    user_rating: usize,\n}\n\nimpl Render for ProductView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_3()\n            .child(\n                Rating::new(\"product-rating\")\n                    .value(self.user_rating)\n                    .max(5)\n                    .on_click(cx.listener(|view, value: &usize, _, cx| {\n                        view.user_rating = *value;\n                        // Save rating to backend, etc.\n                        cx.notify();\n                    }))\n            )\n            .child(format!(\"Your rating: {}/5\", self.user_rating))\n    }\n}\n```\n\n### Large Rating with Custom Color\n\n```rust\nRating::new(\"rating\")\n    .large()\n    .value(5)\n    .max(5)\n    .color(cx.theme().orange)\n```\n\n[Rating]: https://docs.rs/gpui-component/latest/gpui_component/rating/struct.Rating.html\n[Sizable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Sizable.html\n[Disableable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Disableable.html\n"
  },
  {
    "path": "docs/docs/components/resizable.md",
    "content": "---\ntitle: Resizable\ndescription: A flexible panel layout system with draggable resize handles and adjustable panels.\n---\n\n# Resizable\n\nThe resizable component system provides a flexible way to create layouts with resizable panels. It supports both horizontal and vertical resizing, nested layouts, size constraints, and drag handles. Perfect for creating paned interfaces, split views, and adjustable dashboards.\n\n## Import\n\n```rust\nuse gpui_component::resizable::{\n    h_resizable, v_resizable, resizable_panel,\n    ResizablePanelGroup, ResizablePanel, ResizableState, ResizablePanelEvent\n};\n```\n\n## Usage\n\nUse `h_resizable` to create a horizontal layout, `v_resizable` to create a vertical layout.\n\nThe first argument is the `id` for this [ResizablePanelGroup].\n\n:::tip\nIn GPUI, the `id` must be unique within the layout scope (The nearest parent has presents `id`).\n:::\n\n```rust\nh_resizable(\"my-layout\")\n    .on_resize(|state, window, cx| {\n        // Handle resize event\n        // You can read the panel sizes from the state.\n        let state = state.read(cx);\n        let sizes = state.sizes();\n    })\n    .child(\n        // Use resizable_panel() to create a sized panel.\n        resizable_panel()\n            .size(px(200.))\n            .child(\"Left Panel\")\n    )\n    .child(\n        // Or you can just add AnyElement without a size.\n        div()\n            .child(\"Right Panel\")\n            .into_any_element()\n    )\n```\n\nThe `v_resizable` component is used to create a vertical layout.\n\n```rust\nv_resizable(\"vertical-layout\")\n    .child(\n        resizable_panel()\n            .size(px(100.))\n            .child(\"Top Panel\")\n    )\n    .child(\n        div()\n            .child(\"Bottom Panel\")\n            .into_any_element()\n    )\n```\n\n### Panel Size Constraints\n\n```rust\nresizable_panel()\n    .size(px(200.))                    // Initial size\n    .size_range(px(150.)..px(400.))    // Min and max size\n    .child(\"Constrained Panel\")\n```\n\n### Multiple Panels\n\n```rust\nh_resizable(\"multi-panel\", state)\n    .child(\n        resizable_panel()\n            .size(px(200.))\n            .size_range(px(150.)..px(300.))\n            .child(\"Left Panel\")\n    )\n    .child(\n        resizable_panel()\n            .child(\"Center Panel\")\n    )\n    .child(\n        resizable_panel()\n            .size(px(250.))\n            .child(\"Right Panel\")\n    )\n```\n\n### Nested Layouts\n\n```rust\nv_resizable(\"main-layout\", window, cx)\n    .child(\n        resizable_panel()\n            .size(px(300.))\n            .child(\n                h_resizable(\"nested-layout\", window, cx)\n                    .child(\n                        resizable_panel()\n                            .size(px(200.))\n                            .child(\"Top Left\")\n                    )\n                    .child(\n                        resizable_panel()\n                            .child(\"Top Right\")\n                    )\n            )\n    )\n    .child(\n        resizable_panel()\n            .child(\"Bottom Panel\")\n    )\n```\n\n### Nested Panel Groups\n\n```rust\nh_resizable(\"outer\", window, cx)\n    .child(\n        resizable_panel()\n            .size(px(200.))\n            .child(\"Left Panel\")\n    )\n    .group(\n        v_resizable(\"inner\", window, cx)\n            .child(\n                resizable_panel()\n                    .size(px(150.))\n                    .child(\"Top Right\")\n            )\n            .child(\n                resizable_panel()\n                    .child(\"Bottom Right\")\n            )\n    )\n```\n\n### Conditional Panel Visibility\n\n```rust\nresizable_panel()\n    .visible(self.show_sidebar)\n    .size(px(250.))\n    .child(\"Sidebar Content\")\n```\n\n### Panel with Size Limits\n\n```rust\n// Panel with minimum size only\nresizable_panel()\n    .size_range(px(100.)..Pixels::MAX)\n    .child(\"Flexible Panel\")\n\n// Panel with both min and max\nresizable_panel()\n    .size_range(px(200.)..px(500.))\n    .child(\"Constrained Panel\")\n\n// Panel with exact constraints\nresizable_panel()\n    .size(px(300.))\n    .size_range(px(300.)..px(300.))  // Fixed size\n    .child(\"Fixed Panel\")\n```\n\n## Examples\n\n### File Explorer Layout\n\n```rust\nstruct FileExplorer {\n    show_sidebar: bool,\n}\n\nimpl Render for FileExplorer {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        h_resizable(\"file-explorer\", window, cx)\n            .child(\n                resizable_panel()\n                    .visible(self.show_sidebar)\n                    .size(px(250.))\n                    .size_range(px(200.)..px(400.))\n                    .child(\n                        v_flex()\n                            .p_4()\n                            .child(\"📁 Folders\")\n                            .child(\"• Documents\")\n                            .child(\"• Pictures\")\n                            .child(\"• Downloads\")\n                    )\n            )\n            .child(\n                v_flex()\n                    .p_4()\n                    .child(\"📄 Files\")\n                    .child(\"file1.txt\")\n                    .child(\"file2.pdf\")\n                    .child(\"image.png\")\n                    .into_any_element()\n            )\n    }\n}\n```\n\n### IDE Layout\n\n```rust\nstruct IDELayout {\n    main_state: Entity<ResizableState>,\n    sidebar_state: Entity<ResizableState>,\n    bottom_state: Entity<ResizableState>,\n}\n\nimpl Render for IDELayout {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        h_resizable(\"ide-main\", self.main_state.clone())\n            .child(\n                resizable_panel()\n                    .size(px(300.))\n                    .size_range(px(200.)..px(500.))\n                    .child(\n                        v_resizable(\"sidebar\", self.sidebar_state.clone())\n                            .child(\n                                resizable_panel()\n                                    .size(px(200.))\n                                    .child(\"File Explorer\")\n                            )\n                            .child(\n                                resizable_panel()\n                                    .child(\"Outline\")\n                            )\n                    )\n            )\n            .child(\n                resizable_panel()\n                    .child(\n                        v_resizable(\"editor-area\", self.bottom_state.clone())\n                            .child(\n                                resizable_panel()\n                                    .child(\"Code Editor\")\n                            )\n                            .child(\n                                resizable_panel()\n                                    .size(px(150.))\n                                    .size_range(px(100.)..px(300.))\n                                    .child(\"Terminal / Output\")\n                            )\n                    )\n            )\n    }\n}\n```\n\n### Dashboard with Widgets\n\n```rust\nstruct Dashboard {\n    layout_state: Entity<ResizableState>,\n    widget_state: Entity<ResizableState>,\n}\n\nimpl Render for Dashboard {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_resizable(\"dashboard\", self.layout_state.clone())\n            .child(\n                resizable_panel()\n                    .size(px(120.))\n                    .child(\"Header / Navigation\")\n            )\n            .child(\n                resizable_panel()\n                    .child(\n                        h_resizable(\"widgets\", self.widget_state.clone())\n                            .child(\n                                resizable_panel()\n                                    .size(px(300.))\n                                    .child(\"Chart Widget\")\n                            )\n                            .child(\n                                resizable_panel()\n                                    .child(\"Data Table\")\n                            )\n                            .child(\n                                resizable_panel()\n                                    .size(px(250.))\n                                    .child(\"Stats Panel\")\n                            )\n                    )\n            )\n            .child(\n                resizable_panel()\n                    .size(px(60.))\n                    .child(\"Footer\")\n            )\n    }\n}\n```\n\n### Settings Panel\n\n```rust\nstruct SettingsPanel {\n    settings_state: Entity<ResizableState>,\n}\n\nimpl SettingsPanel {\n    fn new(cx: &mut Context<Self>) -> Self {\n        let settings_state = ResizableState::new(cx);\n\n        // Listen for resize events to save layout preferences\n        cx.subscribe(&settings_state, |this, _, event: &ResizablePanelEvent, cx| {\n            match event {\n                ResizablePanelEvent::Resized => {\n                    this.save_layout_preferences(cx);\n                }\n            }\n        });\n\n        Self { settings_state }\n    }\n\n    fn save_layout_preferences(&self, cx: &mut Context<Self>) {\n        let sizes = self.settings_state.read(cx).sizes();\n        // Save to preferences\n        println!(\"Saving layout: {:?}\", sizes);\n    }\n}\n\nimpl Render for SettingsPanel {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        h_resizable(\"settings\", self.settings_state.clone())\n            .child(\n                resizable_panel()\n                    .size(px(200.))\n                    .size_range(px(150.)..px(300.))\n                    .child(\n                        v_flex()\n                            .gap_2()\n                            .p_4()\n                            .child(\"Categories\")\n                            .child(\"• General\")\n                            .child(\"• Appearance\")\n                            .child(\"• Advanced\")\n                    )\n            )\n            .child(\n                resizable_panel()\n                    .child(\n                        div()\n                            .p_6()\n                            .child(\"Settings Content Area\")\n                    )\n            )\n    }\n}\n```\n\n## Best Practices\n\n1. **State Management**: Use separate ResizableState for independent layouts\n2. **Size Constraints**: Always set reasonable min/max sizes for panels\n3. **Event Handling**: Subscribe to ResizablePanelEvent for layout persistence\n4. **Nested Layouts**: Use `.group()` method for clean nested structures\n5. **Performance**: Avoid excessive nesting for better performance\n6. **User Experience**: Provide adequate handle padding for easier interaction\n"
  },
  {
    "path": "docs/docs/components/scrollable.md",
    "content": "---\ntitle: Scrollable\ndescription: Scrollable container with custom scrollbars, scroll tracking, and virtualization support.\n---\n\n# Scrollable\n\nA comprehensive scrollable container component that provides custom scrollbars, scroll tracking, and virtualization capabilities. Supports both vertical and horizontal scrolling with customizable appearance and behavior.\n\n## Import\n\n```rust\nuse gpui_component::{\n    scroll::{ScrollableElement, ScrollbarAxis, ScrollbarShow},\n    StyledExt as _,\n};\n```\n\n## Usage\n\n### Basic Scrollable Container\n\nThe simplest way to make any element scrollable is using the `overflow_scrollbar()` method from `ScrollableElement` trait.\n\nThis method is almost like the `overflow_scroll()` method, but it adds scrollbars.\n\n- `overflow_scrollbar()` - Adds scrollbars for both axes as needed.\n- `overflow_x_scrollbar()` - Adds horizontal scrollbar as needed.\n- `overflow_y_scrollbar()` - Adds vertical scrollbar as needed.\n\n```rust\nuse gpui::{div, Axis};\nuse gpui_component::ScrollableElement;\n\ndiv()\n    .id(\"scrollable-container\")\n    .size_full()\n    .child(\"Your content here\")\n    .overflow_scrollbar()\n```\n\n### Vertical Scrolling\n\n```rust\nv_flex()\n    .id(\"scrollable-container\")\n    .overflow_y_scrollbar()\n    .gap_2()\n    .p_4()\n    .child(\"Scrollable Content\")\n    .children((0..100).map(|i| {\n        div()\n            .h(px(40.))\n            .w_full()\n            .bg(cx.theme().secondary)\n            .child(format!(\"Item {}\", i))\n    }))\n```\n\n### Horizontal Scrolling\n\n```rust\nh_flex()\n    .id(\"scrollable-container\")\n    .overflow_x_scrollbar()\n    .gap_2()\n    .p_4()\n    .children((0..50).map(|i| {\n        div()\n            .min_w(px(120.))\n            .h(px(80.))\n            .bg(cx.theme().accent)\n            .child(format!(\"Card {}\", i))\n    }))\n```\n\n### Both Directions\n\n```rust\ndiv()\n    .id(\"scrollable-container\")\n    .size_full()\n    .overflow_scrollbar()\n    .child(\n        div()\n            .w(px(2000.))  // Wide content\n            .h(px(2000.))  // Tall content\n            .bg(cx.theme().background)\n            .child(\"Large content area\")\n    )\n```\n\n## Custom Scrollbars\n\n### Manual Scrollbar Creation\n\nFor more control, you can create scrollbars manually:\n\n```rust\nuse gpui_component::scroll::{ScrollableElement};\n\npub struct ScrollableView {\n    scroll_handle: ScrollHandle,\n}\n\nimpl Render for ScrollableView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .relative()\n            .size_full()\n            .child(\n                div()\n                    .id(\"content\")\n                    .track_scroll(&self.scroll_handle)\n                    .overflow_scroll()\n                    .size_full()\n                    .child(\"Your scrollable content\")\n            )\n            .vertical_scrollbar(&self.scroll_handle)\n    }\n}\n```\n\n## Virtualization\n\n### VirtualList for Large Datasets\n\nFor rendering large lists efficiently, use `VirtualList`:\n\n```rust\nuse gpui_component::{VirtualList, VirtualListScrollHandle};\n\npub struct LargeListView {\n    items: Vec<String>,\n    scroll_handle: VirtualListScrollHandle,\n}\n\nimpl Render for LargeListView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let item_count = self.items.len();\n\n        VirtualList::new(\n            self.scroll_handle.clone(),\n            item_count,\n            |ix, window, cx| {\n                // Item sizes - can be different for each item\n                size(px(300.), px(40.))\n            },\n            |ix, bounds, selected, window, cx| {\n                // Render each item\n                div()\n                    .size(bounds.size)\n                    .bg(if selected {\n                        cx.theme().accent\n                    } else {\n                        cx.theme().background\n                    })\n                    .child(format!(\"Item {}: {}\", ix, self.items[ix]))\n                    .into_any_element()\n            },\n        )\n    }\n}\n```\n\n### Scrolling to Specific Items\n\n```rust\nimpl LargeListView {\n    fn scroll_to_item(&mut self, index: usize) {\n        self.scroll_handle.scroll_to_item(index, ScrollStrategy::Top);\n    }\n\n    fn scroll_to_item_centered(&mut self, index: usize) {\n        self.scroll_handle.scroll_to_item(index, ScrollStrategy::Center);\n    }\n}\n```\n\n### Variable Item Sizes\n\n```rust\nVirtualList::new(\n    scroll_handle,\n    items.len(),\n    |ix, window, cx| {\n        // Different heights based on content\n        let height = if items[ix].len() > 50 {\n            px(80.)  // Tall items for long content\n        } else {\n            px(40.)  // Normal height\n        };\n        size(px(300.), height)\n    },\n    |ix, bounds, selected, window, cx| {\n        // Render logic\n    },\n)\n```\n\n## Theme Customization\n\n### Scrollbar Appearance\n\nCustomize scrollbar appearance through theme configuration:\n\n```rust\n// In your theme JSON\n{\n    \"scrollbar.background\": \"#ffffff20\",\n    \"scrollbar.thumb.background\": \"#00000060\",\n    \"scrollbar.thumb.hover.background\": \"#000000\"\n}\n```\n\n### Scrollbar Show Modes\n\nControl when scrollbars are visible:\n\n```rust\nuse gpui_component::scroll::ScrollbarShow;\n\n// In theme initialization\ntheme.scrollbar_show = ScrollbarShow::Scrolling;  // Show only when scrolling\ntheme.scrollbar_show = ScrollbarShow::Hover;      // Show on hover\ntheme.scrollbar_show = ScrollbarShow::Always;     // Always visible\n```\n\n### System Integration\n\nSync scrollbar behavior with system preferences:\n\n```rust\n// Automatically sync with system settings\nTheme::sync_scrollbar_appearance(cx);\n```\n\n## Examples\n\n### File Browser with Scrolling\n\n```rust\npub struct FileBrowser {\n    files: Vec<String>,\n}\n\nimpl Render for FileBrowser {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .border_1()\n            .border_color(cx.theme().border)\n            .size_full()\n            .child(\n                v_flex()\n                    .gap_1()\n                    .p_2()\n                    .overflow_y_scrollbar()\n                    .children(self.files.iter().map(|file| {\n                        div()\n                            .h(px(32.))\n                            .w_full()\n                            .px_2()\n                            .flex()\n                            .items_center()\n                            .hover(|style| style.bg(cx.theme().secondary_hover))\n                            .child(file.clone())\n                    }))\n            )\n    }\n}\n```\n\n### Chat Messages with Auto-scroll\n\n```rust\npub struct ChatView {\n    messages: Vec<String>,\n    scroll_handle: ScrollHandle,\n    should_auto_scroll: bool,\n}\n\nimpl ChatView {\n    fn add_message(&mut self, message: String) {\n        self.messages.push(message);\n\n        if self.should_auto_scroll {\n            // Scroll to bottom for new messages\n            let max_offset = self.scroll_handle.max_offset();\n            self.scroll_handle.set_offset(point(px(0.), max_offset.y));\n        }\n    }\n}\n```\n\n### Data Table with Virtual Scrolling\n\n```rust\npub struct DataTable {\n    data: Vec<Vec<String>>,\n    scroll_handle: VirtualListScrollHandle,\n}\n\nimpl Render for DataTable {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        VirtualList::new(\n            self.scroll_handle.clone(),\n            self.data.len(),\n            |_ix, _window, _cx| size(px(800.), px(32.)), // Fixed row height\n            |ix, bounds, _selected, _window, cx| {\n                h_flex()\n                    .size(bounds.size)\n                    .border_b_1()\n                    .border_color(cx.theme().border)\n                    .children(self.data[ix].iter().map(|cell| {\n                        div()\n                            .flex_1()\n                            .px_2()\n                            .flex()\n                            .items_center()\n                            .child(cell.clone())\n                    }))\n                    .into_any_element()\n            },\n        )\n    }\n}\n```\n"
  },
  {
    "path": "docs/docs/components/select.md",
    "content": "---\ntitle: Select\ndescription: Displays a list of options for the user to pick from—triggered by a button.\n---\n\n# Select\n\n:::info\nThis component was named `Dropdown` in `<= 0.3.x`.\n\nIt has been renamed to `Select` to better reflect its purpose.\n:::\n\nA select component that allows users to choose from a list of options.\n\nSupports search functionality, grouped items, custom rendering, and various states. Built with keyboard navigation and accessibility in mind.\n\n## Import\n\n```rust\nuse gpui_component::select::{\n    Select, SelectState, SelectItem, SelectDelegate,\n    SelectEvent, SearchableVec, SelectGroup\n};\n```\n\n## Usage\n\n### Basic Select\n\nYou can create a basic select dropdown by initializing a `SelectState` with a list of items.\n\nThe first type parameter of `SelectState` is the items for the state, which must implement the [SelectItem] trait.\n\nThe built-in implementations of `SelectItem` include common types like `String`, `SharedString`, and `&'static str`.\n\n```rust\nlet state = cx.new(|cx| {\n    SelectState::new(\n        vec![\"Apple\", \"Orange\", \"Banana\"],\n        Some(IndexPath::default()), // Select first item\n        window,\n        cx,\n    )\n});\n\nSelect::new(&state)\n```\n\n### Placeholder\n\n```rust\nlet state = cx.new(|cx| {\n    SelectState::new(\n        vec![\"Rust\", \"Go\", \"JavaScript\"],\n        None, // No initial selection\n        window,\n        cx,\n    )\n});\n\nSelect::new(&state)\n    .placeholder(\"Select a language...\")\n```\n\n### Searchable\n\nUse `searchable(true)` to enable search functionality within the dropdown.\n\n```rust\nlet fruits = SearchableVec::new(vec![\n    \"Apple\", \"Orange\", \"Banana\", \"Grape\", \"Pineapple\",\n]);\n\nlet state = cx.new(|cx| {\n    SelectState::new(fruits, None, window, cx).searchable(true)\n});\n\nSelect::new(&state)\n    .icon(IconName::Search) // Shows search icon\n```\n\n### Impl SelectItem\n\nBy default, we have implmemented `SelectItem` for common types like `String`, `SharedString` and `&'static str`. You can also create your own item types by implementing the `SelectItem` trait.\n\nThis is useful when you want to display complex data structures, and also want get that data type from `select_value` method.\n\nYou can also customize the search logic by overriding the `matches` method.\n\n```rust\n#[derive(Debug, Clone)]\nstruct Country {\n    name: SharedString,\n    code: SharedString,\n}\n\nimpl SelectItem for Country {\n    type Value = SharedString;\n\n    fn title(&self) -> SharedString {\n        self.name.clone()\n    }\n\n    fn display_title(&self) -> Option<gpui::AnyElement> {\n        // Custom display for selected item\n        Some(format!(\"{} ({})\", self.name, self.code).into_any_element())\n    }\n\n    fn value(&self) -> &Self::Value {\n        &self.code\n    }\n\n    fn matches(&self, query: &str) -> bool {\n        // Custom search logic\n        self.name.to_lowercase().contains(&query.to_lowercase()) ||\n        self.code.to_lowercase().contains(&query.to_lowercase())\n    }\n}\n```\n\n### Group Items\n\n```rust\nlet mut grouped_items = SearchableVec::new(vec![]);\n\n// Group countries by first letter\ngrouped_items.push(\n    SelectGroup::new(\"A\")\n        .items(vec![\n            Country { name: \"Australia\".into(), code: \"AU\".into() },\n            Country { name: \"Austria\".into(), code: \"AT\".into() },\n        ])\n);\ngrouped_items.push(\n    SelectGroup::new(\"B\")\n        .items(vec![\n            Country { name: \"Brazil\".into(), code: \"BR\".into() },\n            Country { name: \"Belgium\".into(), code: \"BE\".into() },\n        ])\n);\n\nlet state = cx.new(|cx| {\n    SelectState::new(grouped_items, None, window, cx)\n});\n\nSelect::new(&state)\n```\n\n### Sizes\n\n```rust\nSelect::new(&state).large()\nSelect::new(&state) // medium (default)\nSelect::new(&state).small()\n```\n\n### Disabled State\n\n```rust\nSelect::new(&state).disabled(true)\n```\n\n### Cleanable\n\n```rust\nSelect::new(&state)\n    .cleanable(true) // Show clear button when item is selected\n```\n\n### Custom Appearance\n\n```rust\nSelect::new(&state)\n    .w(px(320.))                    // Set dropdown width\n    .menu_width(px(400.))           // Set menu popup width\n    .appearance(false)              // Remove default styling\n    .title_prefix(\"Country: \")      // Add prefix to selected title\n```\n\n### Empty State\n\n```rust\nlet state = cx.new(|cx| {\n    SelectState::new(Vec::<SharedString>::new(), None, window, cx)\n});\n\nSelect::new(&state)\n    .empty(\n        h_flex()\n            .h_24()\n            .justify_center()\n            .text_color(cx.theme().muted_foreground)\n            .child(\"No options available\")\n    )\n```\n\n### Events\n\n```rust\ncx.subscribe_in(&state, window, |view, state, event, window, cx| {\n    match event {\n        SelectEvent::Confirm(value) => {\n            if let Some(selected_value) = value {\n                println!(\"Selected: {:?}\", selected_value);\n            } else {\n                println!(\"Selection cleared\");\n            }\n        }\n    }\n});\n```\n\n### Mutating\n\n```rust\n// Set by index\nstate.update(cx, |state, cx| {\n    state.set_selected_index(Some(IndexPath::default().row(2)), window, cx);\n});\n\n// Set by value (requires PartialEq on Value type)\nstate.update(cx, |state, cx| {\n    state.set_selected_value(&\"US\".into(), window, cx);\n});\n\n// Get current selection\nlet current_value = state.read(cx).selected_value();\n```\n\nUpdate items:\n\n```rust\nstate.update(cx, |state, cx| {\n    let new_items = vec![\"New Option 1\".into(), \"New Option 2\".into()];\n    state.set_items(new_items, window, cx);\n});\n```\n\n## Examples\n\n### Language Selector\n\n```rust\nlet languages = SearchableVec::new(vec![\n    \"Rust\".into(),\n    \"TypeScript\".into(),\n    \"Go\".into(),\n    \"Python\".into(),\n    \"JavaScript\".into(),\n]);\n\nlet state = cx.new(|cx| {\n    SelectState::new(languages, None, window, cx)\n});\n\nSelect::new(&state)\n    .placeholder(\"Select language...\")\n    .title_prefix(\"Language: \")\n```\n\n### Country/Region Selector\n\n```rust\n#[derive(Debug, Clone)]\nstruct Region {\n    name: SharedString,\n    code: SharedString,\n    flag: SharedString,\n}\n\nimpl SelectItem for Region {\n    type Value = SharedString;\n\n    fn title(&self) -> SharedString {\n        self.name.clone()\n    }\n\n    fn display_title(&self) -> Option<gpui::AnyElement> {\n        Some(\n            h_flex()\n                .items_center()\n                .gap_2()\n                .child(self.flag.clone())\n                .child(format!(\"{} ({})\", self.name, self.code))\n                .into_any_element()\n        )\n    }\n\n    fn value(&self) -> &Self::Value {\n        &self.code\n    }\n}\n\nlet regions = vec![\n    Region {\n        name: \"United States\".into(),\n        code: \"US\".into(),\n        flag: \"🇺🇸\".into()\n    },\n    Region {\n        name: \"Canada\".into(),\n        code: \"CA\".into(),\n        flag: \"🇨🇦\".into()\n    },\n];\n\nlet state = cx.new(|cx| {\n    SelectState::new(regions, None, window, cx)\n});\n\nSelect::new(&state)\n    .placeholder(\"Select country...\")\n```\n\n### Integrated with Input Field\n\n```rust\n// Combined country code + phone input\nh_flex()\n    .border_1()\n    .border_color(cx.theme().input)\n    .rounded(cx.theme().radius_lg)\n    .w_full()\n    .gap_1()\n    .child(\n        div().w(px(140.)).child(\n            Select::new(&country_state)\n                .appearance(false) // No border/background\n                .py_2()\n                .pl_3()\n        )\n    )\n    .child(Divider::vertical())\n    .child(\n        div().flex_1().child(\n            Input::new(&phone_input)\n                .appearance(false)\n                .placeholder(\"Phone number\")\n                .pr_3()\n                .py_2()\n        )\n    )\n```\n\n### Multi-level Grouped Select\n\n```rust\nlet mut grouped_countries = SearchableVec::new(vec![]);\n\nfor (continent, countries) in countries_by_continent {\n    grouped_countries.push(\n        SelectGroup::new(continent)\n            .items(countries)\n    );\n}\n\nlet state = cx.new(|cx| {\n    SelectState::new(grouped_countries, None, window, cx)\n});\n\nSelect::new(&state)\n    .menu_width(px(350.))\n    .placeholder(\"Select country...\")\n```\n\n## Keyboard Shortcuts\n\n| Key       | Action                                  |\n| --------- | --------------------------------------- |\n| `Tab`     | Focus dropdown                          |\n| `Enter`   | Open menu or select current item        |\n| `Up/Down` | Navigate options (opens menu if closed) |\n| `Escape`  | Close menu                              |\n| `Space`   | Open menu                               |\n\n## Theming\n\nThe dropdown respects the current theme and uses the following theme tokens:\n\n- `background` - Dropdown input background\n- `input` - Border color\n- `foreground` - Text color\n- `muted_foreground` - Placeholder and disabled text\n- `accent` - Selected item background\n- `accent_foreground` - Placeholder text color\n- `border` - Menu border\n- `radius` - Border radius\n\n[SelectItem]: https://docs.rs/gpui-component/latest/gpui_component/select/trait.SelectItem.html\n"
  },
  {
    "path": "docs/docs/components/settings.md",
    "content": "---\ntitle: Settings\ndescription: A settings UI with grouped setting items and pages.\n---\n\n# Settings\n\n> Since: v0.5.0\n\nThe Settings component provides a UI for managing application settings. It includes grouped setting items and pages.\nWe can search by title and description to filter the settings to display only relevant settings (Like this macOS, iOS Settings).\n\n## Import\n\n```rust\nuse gpui_component::setting::{Settings, SettingPage, SettingGroup, SettingItem, SettingField};\n```\n\n## Usage\n\n### Build a settings\n\nHere we have components that can be used to build a settings page.\n\n- [Settings] - The main settings component that holds multiple setting pages.\n- [SettingPage] - A page of related setting groups.\n- [SettingGroup] - A group of related setting items based on [GroupBox] style.\n- [SettingItem] - A single setting item with title, description, and field.\n- [SettingField] - Provide different field types like Input, Dropdown, Switch, etc.\n\nThe layout of the settings is like this:\n\n```\nSettings\n  SettingPage\n    SettingGroup\n      SettingItem\n        Title\n        Description (optional)\n        SettingField\n```\n\n### Basic Settings\n\n```rust\nuse gpui_component::setting::{Settings, SettingPage, SettingGroup, SettingItem, SettingField};\n\nSettings::new(\"my-settings\")\n    .pages(vec![\n        SettingPage::new(\"General\")\n            .group(\n                SettingGroup::new()\n                    .title(\"Basic Options\")\n                    .item(\n                        SettingItem::new(\n                            \"Enable Feature\",\n                            SettingField::switch(\n                                |cx: &App| true,\n                                |val: bool, cx: &mut App| {\n                                    println!(\"Feature enabled: {}\", val);\n                                },\n                            )\n                        )\n                    )\n            )\n    ])\n```\n\n### With Multiple Pages\n\n:::info\nWhen you want default expland a page, you can use `default_open(true)` on the [SettingPage].\n:::\n\n```rust\nSettings::new(\"app-settings\")\n    .pages(vec![\n        SettingPage::new(\"General\")\n            .default_open(true)\n            .group(SettingGroup::new().title(\"Appearance\").items(vec![...])),\n        SettingPage::new(\"Software Update\")\n            .group(SettingGroup::new().title(\"Updates\").items(vec![...])),\n        SettingPage::new(\"About\")\n            .group(SettingGroup::new().items(vec![...])),\n    ])\n```\n\n### Group Variants\n\n```rust\nuse gpui_component::group_box::GroupBoxVariant;\n\nSettings::new(\"my-settings\")\n    .with_group_variant(GroupBoxVariant::Outline)\n    .pages(vec![...])\n\nSettings::new(\"my-settings\")\n    .with_group_variant(GroupBoxVariant::Fill)\n    .pages(vec![...])\n```\n\n## Setting Page\n\n### Basic Page\n\n```rust\nSettingPage::new(\"General\")\n    .group(SettingGroup::new().title(\"Options\").items(vec![...]))\n```\n\n### Multiple Groups\n\n```rust\nSettingPage::new(\"General\")\n    .groups(vec![\n        SettingGroup::new().title(\"Appearance\").items(vec![...]),\n        SettingGroup::new().title(\"Font\").items(vec![...]),\n        SettingGroup::new().title(\"Other\").items(vec![...]),\n    ])\n```\n\n### Icon\n\n```rust\nSettingPage::new(\"General\")\n    .icon(IconName::Settings)\n    .groups(vec![...])\n```\n\n### Default Open\n\n```rust\nSettingPage::new(\"General\")\n    .default_open(true)\n    .groups(vec![...])\n```\n\n### resettable\n\nEnable reset functionality for a page:\n\n```rust\nSettingPage::new(\"General\")\n    .resettable(true)\n    .groups(vec![...])\n```\n\n## Setting Group\n\n### Basic Group\n\n```rust\nSettingGroup::new()\n    .title(\"Appearance\")\n    .items(vec![\n        SettingItem::new(...),\n        SettingItem::new(...),\n    ])\n```\n\n### Single Item\n\n```rust\nSettingGroup::new()\n    .title(\"Font\")\n    .item(SettingItem::new(...))\n```\n\n### Without Title\n\n```rust\nSettingGroup::new()\n    .items(vec![...])\n```\n\n## Setting Item\n\n### Basic Item\n\n```rust\nSettingItem::new(\"Title\", SettingField::switch(...))\n    .description(\"Description text\")\n```\n\n### Custom Item with a render closure\n\nYou can create a fully custom setting item using `SettingItem::render`:\n\n```rust\nSettingItem::render(|options, _, _| {\n    h_flex()\n        .w_full()\n        .justify_between()\n        .child(\"Custom content\")\n        .child(\n            Button::new(\"action\")\n                .label(\"Action\")\n                .with_size(options.size)\n        )\n        .into_any_element()\n})\n```\n\n### Vertical Layout\n\nBy default, setting items use horizontal layout. Use `layout(Axis::Vertical)` for vertical layout:\n\n```rust\nSettingItem::new(\n    \"CLI Path\",\n    SettingField::input(...)\n)\n.layout(Axis::Vertical)\n.description(\"This item uses vertical layout.\")\n```\n\n### With Markdown Description\n\n```rust\nuse gpui_component::text::markdown;\n\nSettingItem::new(\n    \"Documentation\",\n    SettingField::element(...)\n)\n.description(markdown(\"Rust doc for the `gpui-component` crate.\"))\n```\n\n## Setting Fields\n\nThe [SettingField] enum provides different field types for various input needs.\n\n### Switch\n\nThe switch field represents a `boolean` on/off state.\n\n```rust\nSettingItem::new(\n    \"Dark Mode\",\n    SettingField::switch(\n        |cx: &App| cx.theme().mode.is_dark(),\n        |val: bool, cx: &mut App| {\n            // Handle value change\n        },\n    )\n    .default_value(false)\n)\n```\n\n### Checkbox\n\nLike the switch, but uses a checkbox UI.\n\n```rust\nSettingItem::new(\n    \"Auto Switch Theme\",\n    SettingField::checkbox(\n        |cx: &App| AppSettings::global(cx).auto_switch_theme,\n        |val: bool, cx: &mut App| {\n            AppSettings::global_mut(cx).auto_switch_theme = val;\n        },\n    )\n    .default_value(false)\n)\n```\n\n### Input\n\nDisplay a single line text input.\n\n```rust\nSettingItem::new(\n    \"CLI Path\",\n    SettingField::input(\n        |cx: &App| AppSettings::global(cx).cli_path.clone(),\n        |val: SharedString, cx: &mut App| {\n            AppSettings::global_mut(cx).cli_path = val;\n        },\n    )\n    .default_value(\"/usr/local/bin/bash\".into())\n)\n.layout(Axis::Vertical)\n.description(\"Path to the CLI executable.\")\n```\n\n### Dropdown\n\nA dropdown with a list of options.\n\n```rust\nSettingItem::new(\n    \"Font Family\",\n    SettingField::dropdown(\n        vec![\n            (\"Arial\".into(), \"Arial\".into()),\n            (\"Helvetica\".into(), \"Helvetica\".into()),\n            (\"Times New Roman\".into(), \"Times New Roman\".into()),\n        ],\n        |cx: &App| AppSettings::global(cx).font_family.clone(),\n        |val: SharedString, cx: &mut App| {\n            AppSettings::global_mut(cx).font_family = val;\n        },\n    )\n    .default_value(\"Arial\".into())\n)\n```\n\n### NumberInput\n\n```rust\nuse gpui_component::setting::NumberFieldOptions;\n\nSettingItem::new(\n    \"Font Size\",\n    SettingField::number_input(\n        NumberFieldOptions {\n            min: 8.0,\n            max: 72.0,\n            ..Default::default()\n        },\n        |cx: &App| AppSettings::global(cx).font_size,\n        |val: f64, cx: &mut App| {\n            AppSettings::global_mut(cx).font_size = val;\n        },\n    )\n    .default_value(14.0)\n)\n```\n\n### Custom Field by Render Closure\n\nThe `SettingField::render` method allows you to create a custom field using a closure that returns an element.\n\n```rust\nSettingItem::new(\n    \"GitHub Repository\",\n    SettingField::render(|options, _window, _cx| {\n        Button::new(\"open-url\")\n            .outline()\n            .label(\"Repository...\")\n            .with_size(options.size)\n            .on_click(|_, _window, cx| {\n                cx.open_url(\"https://github.com/example/repo\");\n            })\n    })\n)\n```\n\n### Custom Field Element\n\nYou may have a complex field that you want to reuse, you may want split the element into a separate struct to do the complex logic.\n\nIn this case, the [SettingFieldElement] trait can help you to create a custom field element.\n\n```rust\nuse gpui_component::setting::{SettingFieldElement, RenderOptions};\n\nstruct OpenURLSettingField {\n    label: SharedString,\n    url: SharedString,\n}\n\nimpl SettingFieldElement for OpenURLSettingField {\n    type Element = Button;\n\n    fn render_field(&self, options: &RenderOptions, _: &mut Window, _: &mut App) -> Self::Element {\n        let url = self.url.clone();\n        Button::new(\"open-url\")\n            .outline()\n            .label(self.label.clone())\n            .with_size(options.size)\n            .on_click(move |_, _window, cx| {\n                cx.open_url(url.as_str());\n            })\n    }\n}\n```\n\nThen use it in the setting item:\n\n```rust\nSettingItem::new(\n    \"GitHub Repository\",\n    SettingField::element(OpenURLSettingField {\n        label: \"Repository...\".into(),\n        url: \"https://github.com/longbridge/gpui-component\".into(),\n    })\n)\n```\n\n## API Reference\n\n- [Settings]\n- [SettingPage]\n- [SettingGroup]\n- [SettingItem]\n- [SettingField]\n- [NumberFieldOptions]\n\n### Sizing\n\nImplements [Sizable] trait:\n\n- `xsmall()` - Extra small size\n- `small()` - Small size\n- `medium()` - Medium size (default)\n- `large()` - Large size\n- `with_size(Size)` - Set specific size\n\n## Examples\n\n### Complete Settings Example\n\n```rust\nuse gpui::{App, SharedString};\nuse gpui_component::{\n    Settings, SettingPage, SettingGroup, SettingItem, SettingField,\n    setting::NumberFieldOptions,\n    group_box::GroupBoxVariant,\n    Size,\n};\n\nSettings::new(\"app-settings\")\n    .with_size(Size::Medium)\n    .with_group_variant(GroupBoxVariant::Outline)\n    .pages(vec![\n        SettingPage::new(\"General\")\n            .resettable(true)\n            .default_open(true)\n            .groups(vec![\n                SettingGroup::new()\n                    .title(\"Appearance\")\n                    .items(vec![\n                        SettingItem::new(\n                            \"Dark Mode\",\n                            SettingField::switch(\n                                |cx: &App| cx.theme().mode.is_dark(),\n                                |val: bool, cx: &mut App| {\n                                    // Handle theme change\n                                },\n                            )\n                        )\n                        .description(\"Switch between light and dark themes.\"),\n                    ]),\n                SettingGroup::new()\n                    .title(\"Font\")\n                    .items(vec![\n                        SettingItem::new(\n                            \"Font Family\",\n                            SettingField::dropdown(\n                                vec![\n                                    (\"Arial\".into(), \"Arial\".into()),\n                                    (\"Helvetica\".into(), \"Helvetica\".into()),\n                                ],\n                                |cx: &App| \"Arial\".into(),\n                                |val: SharedString, cx: &mut App| {\n                                    // Handle font change\n                                },\n                            )\n                        ),\n                        SettingItem::new(\n                            \"Font Size\",\n                            SettingField::number_input(\n                                NumberFieldOptions {\n                                    min: 8.0,\n                                    max: 72.0,\n                                    ..Default::default()\n                                },\n                                |cx: &App| 14.0,\n                                |val: f64, cx: &mut App| {\n                                    // Handle size change\n                                },\n                            )\n                        ),\n                    ]),\n            ]),\n        SettingPage::new(\"Software Update\")\n            .resettable(true)\n            .group(\n                SettingGroup::new()\n                    .title(\"Updates\")\n                    .items(vec![\n                        SettingItem::new(\n                            \"Auto Update\",\n                            SettingField::switch(\n                                |cx: &App| true,\n                                |val: bool, cx: &mut App| {\n                                    // Handle auto update\n                                },\n                            )\n                        )\n                        .description(\"Automatically download and install updates.\"),\n                    ])\n            ),\n    ])\n```\n\n[Settings]: https://docs.rs/gpui-component/latest/gpui_component/setting/struct.Settings.html\n[SettingPage]: https://docs.rs/gpui-component/latest/gpui_component/setting/struct.SettingPage.html\n[SettingGroup]: https://docs.rs/gpui-component/latest/gpui_component/setting/struct.SettingGroup.html\n[SettingItem]: https://docs.rs/gpui-component/latest/gpui_component/setting/struct.SettingItem.html\n[SettingField]: https://docs.rs/gpui-component/latest/gpui_component/setting/enum.SettingField.html\n[SettingFieldElement]: https://docs.rs/gpui-component/latest/gpui_component/setting/trait.SettingFieldElement.html\n[NumberFieldOptions]: https://docs.rs/gpui-component/latest/gpui_component/setting/struct.NumberFieldOptions.html\n[GroupBox]: ./group-box.md\n[Sizable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Sizable.html\n"
  },
  {
    "path": "docs/docs/components/sheet.md",
    "content": "---\ntitle: Sheet\ndescription: A sliding panel that appears from the edges of the screen for displaying content.\n---\n\n# Sheet\n\nA Sheet (also known as a sidebar or slide-out panel) is a navigation component that slides in from the edges of the screen. It provides additional space for content without taking up the main view, and can be used for navigation menus, forms, or any supplementary content.\n\n## Import\n\n```rust\nuse gpui_component::WindowExt;\nuse gpui_component::Placement;\n```\n\n## Usage\n\n### Setup application root view for display of sheets\n\nYou need to set up your application's root view to render the sheet layer. This is typically done in your main application struct's render method.\n\nThe [Root::render_sheet_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_sheet_layer) function handles rendering any active modals on top of your app content.\n\n```rust\nuse gpui_component::TitleBar;\n\nstruct MyApp {\n    view: AnyView,\n}\n\nimpl Render for MyApp {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let sheet_layer = Root::render_sheet_layer(window, cx);\n\n        div()\n            .size_full()\n            .child(\n                v_flex()\n                    .size_full()\n                    .child(TitleBar::new())\n                    .child(div().flex_1().overflow_hidden().child(self.view.clone())),\n            )\n            // Render the sheet layer on top of the app content\n            .children(sheet_layer)\n    }\n}\n```\n\n### Basic Sheet\n\n```rust\nwindow.open_sheet(cx, |sheet, _, _| {\n    sheet\n        .title(\"Navigation\")\n        .child(\"Sheet content goes here\")\n})\n```\n\n### Sheet with Placement\n\n```rust\n// Left sheet (default)\nwindow.open_sheet_at(Placement::Left, cx, |sheet, _, _| {\n    sheet.title(\"Left Sheet\")\n})\n\n// Right sheet\nwindow.open_sheet_at(Placement::Right, cx, |sheet, _, _| {\n    sheet.title(\"Right Sheet\")\n})\n\n// Top sheet\nwindow.open_sheet_at(Placement::Top, cx, |sheet, _, _| {\n    sheet.title(\"Top Sheet\")\n})\n\n// Bottom sheet\nwindow.open_sheet_at(Placement::Bottom, cx, |sheet, _, _| {\n    sheet.title(\"Bottom Sheet\")\n})\n```\n\n### Sheet with Custom Size\n\n```rust\nwindow.open_sheet(cx, |sheet, _, _| {\n    sheet\n        .title(\"Wide Sheet\")\n        .size(px(500.))  // Custom width for left/right, height for top/bottom\n        .child(\"This sheet is 500px wide\")\n})\n```\n\n### Sheet with Form Content\n\n```rust\nlet input = cx.new(|cx| InputState::new(window, cx));\nlet date = cx.new(|cx| DatePickerState::new(window, cx));\n\nwindow.open_sheet(cx, |sheet, _, _| {\n    sheet\n        .title(\"User Profile\")\n        .child(\n            v_flex()\n                .gap_4()\n                .child(\"Enter your information:\")\n                .child(Input::new(&input).placeholder(\"Full Name\"))\n                .child(DatePicker::new(&date).placeholder(\"Date of Birth\"))\n        )\n        .footer(\n            h_flex()\n                .gap_3()\n                .child(Button::new(\"save\").primary().label(\"Save\"))\n                .child(Button::new(\"cancel\").label(\"Cancel\"))\n        )\n})\n```\n\n### Overlay Options\n\n```rust\nwindow.open_sheet(cx, |sheet, _, _| {\n    sheet\n        .title(\"Settings\")\n        .overlay(true)              // Show overlay background (default: true)\n        .overlay_closable(true)     // Click overlay to close (default: true)\n        .child(\"Sheet settings content\")\n})\n\n// No overlay\nwindow.open_sheet(cx, |sheet, _, _| {\n    sheet\n        .title(\"Side Panel\")\n        .overlay(false)             // No overlay background\n        .child(\"This sheet has no overlay\")\n})\n```\n\n### Resizable Sheet\n\n```rust\nwindow.open_sheet(cx, |sheet, _, _| {\n    sheet\n        .title(\"Resizable Panel\")\n        .resizable(true)            // Allow user to resize (default: true)\n        .size(px(300.))\n        .child(\"You can resize this sheet by dragging the edge\")\n})\n```\n\n### Custom Margin and Positioning\n\n```rust\nwindow.open_sheet(cx, |sheet, _, _| {\n    sheet\n        .title(\"Below Title Bar\")\n        .margin_top(px(32.))        // Space for window title bar\n        .child(\"This sheet appears below the title bar\")\n})\n```\n\n### Sheet with List\n\n```rust\nlet delegate = ListDelegate::new(items);\nlet list = cx.new(|cx| List::new(delegate, window, cx));\n\nwindow.open_sheet_at(Placement::Left, cx, |sheet, _, _| {\n    sheet\n        .title(\"File Explorer\")\n        .size(px(400.))\n        .child(\n            div()\n                .border_1()\n                .border_color(cx.theme().border)\n                .rounded(cx.theme().radius)\n                .size_full()\n                .child(list.clone())\n        )\n})\n```\n\n### Close Event Handling\n\n```rust\nwindow.open_sheet(cx, |sheet, _, _| {\n    sheet\n        .title(\"Sheet with Handler\")\n        .child(\"This sheet has a custom close handler\")\n        .on_close(|_, window, cx| {\n            window.push_notification(\"Sheet was closed\", cx);\n        })\n})\n```\n\n### Navigation Sheet\n\n```rust\nwindow.open_sheet_at(Placement::Left, cx, |sheet, _, _| {\n    sheet\n        .title(\"Navigation\")\n        .size(px(280.))\n        .child(\n            v_flex()\n                .gap_2()\n                .child(Button::new(\"home\").ghost().label(\"Home\").w_full())\n                .child(Button::new(\"profile\").ghost().label(\"Profile\").w_full())\n                .child(Button::new(\"settings\").ghost().label(\"Settings\").w_full())\n                .child(Button::new(\"logout\").ghost().label(\"Logout\").w_full())\n        )\n})\n```\n\n### Custom Styling\n\n```rust\nwindow.open_sheet(cx, |sheet, _, cx| {\n    sheet\n        .title(\"Styled Sheet\")\n        .bg(cx.theme().accent)\n        .text_color(cx.theme().accent_foreground)\n        .border_color(cx.theme().primary)\n        .child(\"Custom styled sheet content\")\n})\n```\n\n### Programmatic Close\n\n```rust\n// Close sheet from inside\nButton::new(\"close\")\n    .label(\"Close Sheet\")\n    .on_click(|_, window, cx| {\n        window.close_sheet(cx);\n    })\n\n// Close sheet from outside\nwindow.close_sheet(cx);\n```\n\n## API Reference\n\n### Window Extensions\n\n| Method                             | Description                               |\n| ---------------------------------- | ----------------------------------------- |\n| `open_sheet(cx, fn)`               | Open sheet with default placement (Right) |\n| `open_sheet_at(placement, cx, fn)` | Open sheet at specific placement          |\n| `close_sheet(cx)`                  | Close current sheet                       |\n\n### Sheet Builder\n\n| Method                   | Description                             |\n| ------------------------ | --------------------------------------- |\n| `title(str)`             | Set sheet title                         |\n| `child(el)`              | Add content to sheet body               |\n| `footer(el)`             | Set footer content                      |\n| `size(px)`               | Set sheet size (width or height)        |\n| `margin_top(px)`         | Set top margin (for title bars)         |\n| `resizable(bool)`        | Allow resizing (default: true)          |\n| `overlay(bool)`          | Show overlay background (default: true) |\n| `overlay_closable(bool)` | Click overlay to close (default: true)  |\n| `on_close(fn)`           | Close event callback                    |\n\n### Placement Options\n\n| Value               | Description                         |\n| ------------------- | ----------------------------------- |\n| `Placement::Left`   | Slides in from left edge            |\n| `Placement::Right`  | Slides in from right edge (default) |\n| `Placement::Top`    | Slides in from top edge             |\n| `Placement::Bottom` | Slides in from bottom edge          |\n\n### Styling Methods\n\n| Method                | Description              |\n| --------------------- | ------------------------ |\n| `bg(color)`           | Set background color     |\n| `text_color(color)`   | Set text color           |\n| `border_color(color)` | Set border color         |\n| `px_*()/py_*()`       | Custom padding           |\n| `gap_*()`             | Spacing between children |\n\n## Examples\n\n### Settings Panel\n\n```rust\nwindow.open_sheet_at(Placement::Right, cx, |sheet, _, _| {\n    sheet\n        .title(\"Settings\")\n        .size(px(350.))\n        .child(\n            v_flex()\n                .gap_4()\n                .child(\"Appearance\")\n                .child(Checkbox::new(\"dark-mode\").label(\"Dark Mode\"))\n                .child(Checkbox::new(\"animations\").label(\"Enable Animations\"))\n                .child(\"Notifications\")\n                .child(Checkbox::new(\"push-notifications\").label(\"Push Notifications\"))\n        )\n        .footer(\n            h_flex()\n                .justify_end()\n                .gap_2()\n                .child(Button::new(\"apply\").primary().label(\"Apply\"))\n                .child(Button::new(\"cancel\").label(\"Cancel\"))\n        )\n})\n```\n\n### File Browser\n\n```rust\nwindow.open_sheet_at(Placement::Left, cx, |sheet, _, _| {\n    sheet\n        .title(\"Files\")\n        .size(px(300.))\n        .child(\n            v_flex()\n                .size_full()\n                .child(\n                    h_flex()\n                        .gap_2()\n                        .p_2()\n                        .child(Button::new(\"new-folder\").small().icon(IconName::FolderPlus))\n                        .child(Button::new(\"upload\").small().icon(IconName::Upload))\n                )\n                .child(\n                    div()\n                        .flex_1()\n                        .overflow_hidden()\n                        .child(file_tree_list)\n                )\n        )\n})\n```\n\n### Help Panel\n\n```rust\nwindow.open_sheet_at(Placement::Bottom, cx, |sheet, _, _| {\n    sheet\n        .title(\"Help & Documentation\")\n        .size(px(200.))\n        .child(\n            h_flex()\n                .gap_4()\n                .child(\"Keyboard Shortcuts\")\n                .child(Kbd::new(\"⌘\").child(\"K\"))\n                .child(\"Search\")\n                .child(Kbd::new(\"⌘\").child(\"P\"))\n                .child(\"Command Palette\")\n        )\n})\n```\n\n## Best Practices\n\n1. **Appropriate Placement**: Use left/right for navigation, top/bottom for temporary content\n2. **Consistent Sizing**: Maintain consistent sheet sizes across your application\n3. **Clear Headers**: Always provide descriptive titles\n4. **Close Options**: Provide multiple ways to close (ESC, overlay click, close button)\n5. **Content Organization**: Use proper spacing and grouping for sheet content\n6. **Responsive Design**: Consider sheet behavior on smaller screens\n7. **Performance**: Lazy load sheet content when possible for better performance\n"
  },
  {
    "path": "docs/docs/components/sidebar.md",
    "content": "---\ntitle: Sidebar\ndescription: A composable, themeable and customizable sidebar component for navigation and content organization.\n---\n\n# Sidebar\n\nA flexible sidebar component that provides navigation structure for applications. Features collapsible states, nested menu items, header and footer sections, and responsive design. Perfect for creating application navigation panels, admin dashboards, and complex hierarchical interfaces.\n\n## Import\n\n```rust\nuse gpui_component::sidebar::{\n    Sidebar, SidebarHeader, SidebarFooter, SidebarGroup,\n    SidebarMenu, SidebarMenuItem, SidebarToggleButton\n};\n```\n\n## Usage\n\n### Basic Sidebar\n\n```rust\nuse gpui_component::{sidebar::*, Side};\n\nSidebar::new()\n    .header(\n        SidebarHeader::new()\n            .child(\"My Application\")\n    )\n    .child(\n        SidebarGroup::new(\"Navigation\")\n            .child(\n                SidebarMenu::new()\n                    .child(\n                        SidebarMenuItem::new(\"Dashboard\")\n                            .icon(IconName::LayoutDashboard)\n                            .on_click(|_, _, _| println!(\"Dashboard clicked\"))\n                    )\n                    .child(\n                        SidebarMenuItem::new(\"Settings\")\n                            .icon(IconName::Settings)\n                            .on_click(|_, _, _| println!(\"Settings clicked\"))\n                    )\n            )\n    )\n    .footer(\n        SidebarFooter::new()\n            .child(\"User Profile\")\n    )\n```\n\n### Collapsible Sidebar\n\n```rust\nlet mut collapsed = false;\n\nSidebar::new()\n    .collapsed(collapsed)\n    .collapsible(true)\n    .header(\n        SidebarHeader::new()\n            .child(\n                h_flex()\n                    .child(Icon::new(IconName::Home))\n                    .when(!collapsed, |this| this.child(\"Home\"))\n            )\n    )\n    .child(\n        SidebarGroup::new(\"Menu\")\n            .child(\n                SidebarMenu::new()\n                    .child(\n                        SidebarMenuItem::new(\"Files\")\n                            .icon(IconName::Folder)\n                    )\n            )\n    )\n\n// Toggle button\nSidebarToggleButton::new()\n    .collapsed(collapsed)\n    .on_click(|_, _, _| {\n        collapsed = !collapsed;\n    })\n```\n\n### Nested Menu Items\n\n```rust\nSidebarMenuItem::new(\"Projects\")\n    .icon(IconName::FolderOpen)\n    .active(true)\n    .children([\n        SidebarMenuItem::new(\"Web App\")\n            .active(false)\n            .on_click(|_, _, _| println!(\"Web App selected\")),\n        SidebarMenuItem::new(\"Mobile App\")\n            .active(true)\n            .on_click(|_, _, _| println!(\"Mobile App selected\")),\n        SidebarMenuItem::new(\"Desktop App\")\n            .on_click(|_, _, _| println!(\"Desktop App selected\")),\n    ])\n    .on_click(|_, _, _| {\n        // Toggle project group\n    })\n```\n\n### Multiple Groups\n\n```rust\nSidebar::new()\n    .child(\n        SidebarGroup::new(\"Main\")\n            .child(\n                SidebarMenu::new()\n                    .child(SidebarMenuItem::new(\"Dashboard\").icon(IconName::Home))\n                    .child(SidebarMenuItem::new(\"Analytics\").icon(IconName::BarChart))\n            )\n    )\n    .child(\n        SidebarGroup::new(\"Content\")\n            .child(\n                SidebarMenu::new()\n                    .child(SidebarMenuItem::new(\"Posts\").icon(IconName::FileText))\n                    .child(SidebarMenuItem::new(\"Media\").icon(IconName::Image))\n                    .child(SidebarMenuItem::new(\"Comments\").icon(IconName::MessageCircle))\n            )\n    )\n    .child(\n        SidebarGroup::new(\"Settings\")\n            .child(\n                SidebarMenu::new()\n                    .child(SidebarMenuItem::new(\"General\").icon(IconName::Settings))\n                    .child(SidebarMenuItem::new(\"Users\").icon(IconName::Users))\n            )\n    )\n```\n\n### With Badges and Suffixes\n\n```rust\nuse gpui_component::{Badge, Switch};\n\nSidebarMenuItem::new(\"Notifications\")\n    .icon(IconName::Bell)\n    .suffix(\n        Badge::new()\n            .count(5)\n            .child(\"5\")\n    )\n\nSidebarMenuItem::new(\"Dark Mode\")\n    .icon(IconName::Moon)\n    .suffix(\n        Switch::new(\"dark-mode\")\n            .checked(true)\n            .xsmall()\n    )\n\nSidebarMenuItem::new(\"Settings\")\n    .icon(IconName::Settings)\n    .suffix(IconName::ChevronRight)\n```\n\n### Right-Side Placement\n\n```rust\nSidebar::new()\n    .side(Side::Right)\n    .width(300)\n    .header(\n        SidebarHeader::new()\n            .child(\"Right Panel\")\n    )\n    .child(\n        SidebarGroup::new(\"Tools\")\n            .child(\n                SidebarMenu::new()\n                    .child(SidebarMenuItem::new(\"Inspector\").icon(IconName::Search))\n                    .child(SidebarMenuItem::new(\"Console\").icon(IconName::Terminal))\n            )\n    )\n```\n\n### Context Menus\n\nAdd right-click context menus to sidebar menu items for additional actions:\n\n```rust\nuse gpui_component::menu::PopupMenu;\n\nSidebarMenuItem::new(\"Project Files\")\n    .icon(IconName::Folder)\n    .context_menu(|menu, _, _| {\n        menu.link(\"Open in Editor\", \"https://editor.example.com\")\n            .separator()\n            .menu_with_description(\"Rename\", \"Rename this project\", Box::new(RenameAction))\n            .menu_with_description(\"Delete\", \"Delete this project\", Box::new(DeleteAction))\n            .separator()\n            .submenu(\"Share\", |submenu| {\n                submenu.menu(\"Copy Link\", Box::new(CopyLinkAction))\n                       .menu(\"Send via Email\", Box::new(EmailAction))\n            })\n    })\n\n// Multiple items with context menus\nSidebarMenu::new()\n    .child(\n        SidebarMenuItem::new(\"Documentation\")\n            .icon(IconName::BookOpen)\n            .context_menu(|menu, _, _| {\n                menu.menu(\"View Online\", Box::new(ViewOnlineAction))\n                    .menu(\"Download PDF\", Box::new(DownloadPdfAction))\n            })\n    )\n    .child(\n        SidebarMenuItem::new(\"Settings\")\n            .icon(IconName::Settings)\n            .children([\n                SidebarMenuItem::new(\"General\")\n                    .context_menu(|menu, _, _| {\n                        menu.menu(\"Reset to Defaults\", Box::new(ResetAction))\n                    }),\n                SidebarMenuItem::new(\"Advanced\")\n                    .context_menu(|menu, _, _| {\n                        menu.menu(\"Export Settings\", Box::new(ExportAction))\n                            .menu(\"Import Settings\", Box::new(ImportAction))\n                    })\n            ])\n    )\n```\n\n### Custom Width and Styling\n\n```rust\nSidebar::new()\n    .width(280)  // Custom width in pixels\n    .border_width(2)  // Custom border width\n    .header(\n        SidebarHeader::new()\n            .p_4()  // Custom padding\n            .rounded(cx.theme().radius)\n            .child(\"Custom Styled Sidebar\")\n    )\n```\n\n### Interactive Header with Popup Menu\n\n```rust\nuse gpui_component::menu::DropdownMenu;\n\nSidebarHeader::new()\n    .child(\n        h_flex()\n            .gap_2()\n            .child(Icon::new(IconName::Building))\n            .child(\"Company Name\")\n            .child(Icon::new(IconName::ChevronsUpDown))\n    )\n    .dropdown_menu(|menu, _, _| {\n        menu.menu(\"Acme Corp\", Box::new(SelectCompany(\"acme\")))\n            .menu(\"Tech Solutions\", Box::new(SelectCompany(\"tech\")))\n            .separator()\n            .menu(\"Switch Organization\", Box::new(SwitchOrg))\n    })\n```\n\n### Footer with User Information\n\n```rust\nSidebarFooter::new()\n    .justify_between()\n    .child(\n        h_flex()\n            .gap_2()\n            .child(Icon::new(IconName::User))\n            .when(!collapsed, |this| {\n                this.child(\n                    v_flex()\n                        .child(\"John Doe\")\n                        .child(div().text_xs().text_color(cx.theme().muted_foreground).child(\"john@example.com\"))\n                )\n            })\n    )\n    .when(!collapsed, |this| {\n        this.child(Icon::new(IconName::MoreHorizontal))\n    })\n```\n\n### Responsive Sidebar\n\n```rust\nlet is_mobile = window_width < 768;\n\nSidebar::new()\n    .collapsed(is_mobile || manually_collapsed)\n    .width(if is_mobile { 60 } else { 240 })\n    .header(\n        SidebarHeader::new()\n            .child(\n                div()\n                    .when(!is_mobile, |this| this.child(\"Full App Name\"))\n                    .when(is_mobile, |this| this.child(Icon::new(IconName::Menu)))\n            )\n    )\n```\n\n## Theming\n\nThe sidebar uses dedicated theme colors:\n\n```rust\n// Theme colors used by sidebar\ncx.theme().sidebar                    // Background\ncx.theme().sidebar_foreground         // Text color\ncx.theme().sidebar_border            // Border color\ncx.theme().sidebar_accent            // Hover/active background\ncx.theme().sidebar_accent_foreground // Hover/active text\ncx.theme().sidebar_primary           // Primary elements\ncx.theme().sidebar_primary_foreground // Primary text\n```\n\n## Examples\n\n### File Explorer Sidebar\n\n```rust\nSidebar::new()\n    .header(\n        SidebarHeader::new()\n            .child(\n                h_flex()\n                    .gap_2()\n                    .child(IconName::Folder)\n                    .child(\"Explorer\")\n            )\n    )\n    .child(\n        SidebarGroup::new(\"Folders\")\n            .child(\n                SidebarMenu::new()\n                    .child(\n                        SidebarMenuItem::new(\"src\")\n                            .icon(IconName::FolderOpen)\n                            .active(true)\n                            .children([\n                                SidebarMenuItem::new(\"components\")\n                                    .icon(IconName::Folder),\n                                SidebarMenuItem::new(\"utils\")\n                                    .icon(IconName::Folder),\n                                SidebarMenuItem::new(\"main.rs\")\n                                    .icon(IconName::FileCode)\n                                    .active(true),\n                            ])\n                    )\n                    .child(\n                        SidebarMenuItem::new(\"tests\")\n                            .icon(IconName::Folder)\n                    )\n                    .child(\n                        SidebarMenuItem::new(\"Cargo.toml\")\n                            .icon(IconName::FileText)\n                    )\n            )\n    )\n```\n\n### Admin Dashboard Sidebar\n\n```rust\nSidebar::new()\n    .header(\n        SidebarHeader::new()\n            .child(\n                h_flex()\n                    .gap_2()\n                    .child(\n                        div()\n                            .size_8()\n                            .rounded_full()\n                            .bg(cx.theme().primary)\n                            .child(Icon::new(IconName::Crown))\n                    )\n                    .child(\"Admin Panel\")\n            )\n    )\n    .child(\n        SidebarGroup::new(\"Overview\")\n            .child(\n                SidebarMenu::new()\n                    .child(\n                        SidebarMenuItem::new(\"Dashboard\")\n                            .icon(IconName::LayoutDashboard)\n                            .active(true)\n                    )\n                    .child(\n                        SidebarMenuItem::new(\"Analytics\")\n                            .icon(IconName::TrendingUp)\n                            .suffix(Badge::new().count(2))\n                    )\n            )\n    )\n    .child(\n        SidebarGroup::new(\"Management\")\n            .child(\n                SidebarMenu::new()\n                    .child(\n                        SidebarMenuItem::new(\"Users\")\n                            .icon(IconName::Users)\n                            .suffix(\"1,234\")\n                    )\n                    .child(\n                        SidebarMenuItem::new(\"Orders\")\n                            .icon(IconName::ShoppingCart)\n                            .suffix(Badge::new().dot().variant_destructive())\n                    )\n                    .child(\n                        SidebarMenuItem::new(\"Products\")\n                            .icon(IconName::Package)\n                    )\n            )\n    )\n    .footer(\n        SidebarFooter::new()\n            .child(\n                h_flex()\n                    .gap_2()\n                    .child(IconName::User)\n                    .child(\"Administrator\")\n            )\n            .child(IconName::LogOut)\n    )\n```\n\n### Settings Sidebar\n\n```rust\nSidebar::new()\n    .width(300)\n    .header(\n        SidebarHeader::new()\n            .child(\"Settings\")\n    )\n    .child(\n        SidebarGroup::new(\"General\")\n            .child(\n                SidebarMenu::new()\n                    .child(\n                        SidebarMenuItem::new(\"Appearance\")\n                            .icon(IconName::Palette)\n                            .active(true)\n                    )\n                    .child(\n                        SidebarMenuItem::new(\"Notifications\")\n                            .icon(IconName::Bell)\n                            .suffix(\n                                Switch::new(\"notifications\")\n                                    .checked(true)\n                                    .xsmall()\n                            )\n                    )\n                    .child(\n                        SidebarMenuItem::new(\"Privacy\")\n                            .icon(IconName::Shield)\n                    )\n            )\n    )\n    .child(\n        SidebarGroup::new(\"Advanced\")\n            .child(\n                SidebarMenu::new()\n                    .child(\n                        SidebarMenuItem::new(\"Developer\")\n                            .icon(IconName::Code)\n                            .children([\n                                SidebarMenuItem::new(\"Debug Mode\")\n                                    .suffix(\n                                        Switch::new(\"debug\")\n                                            .checked(false)\n                                            .xsmall()\n                                    ),\n                                SidebarMenuItem::new(\"Console\")\n                                    .on_click(|_, _, _| println!(\"Open console\")),\n                            ])\n                    )\n                    .child(\n                        SidebarMenuItem::new(\"Performance\")\n                            .icon(IconName::Zap)\n                    )\n            )\n    )\n```\n"
  },
  {
    "path": "docs/docs/components/skeleton.md",
    "content": "---\ntitle: Skeleton\ndescription: Use to show a placeholder while content is loading.\n---\n\n# Skeleton\n\nThe Skeleton component displays animated placeholder content while actual content is loading. It provides visual feedback to users that content is being loaded and helps maintain layout structure during loading states.\n\n## Import\n\n```rust\nuse gpui_component::skeleton::Skeleton;\n```\n\n## Usage\n\n### Basic Skeleton\n\n```rust\nSkeleton::new()\n```\n\n### Text Line Skeleton\n\n```rust\n// Single line of text\nSkeleton::new()\n    .w(px(250.))\n    .h_4()\n    .rounded_md()\n\n// Multiple text lines\nv_flex()\n    .gap_2()\n    .child(Skeleton::new().w(px(250.)).h_4().rounded_md())\n    .child(Skeleton::new().w(px(200.)).h_4().rounded_md())\n    .child(Skeleton::new().w(px(180.)).h_4().rounded_md())\n```\n\n### Circle Skeleton\n\n```rust\n// Avatar placeholder\nSkeleton::new()\n    .size_12()\n    .rounded_full()\n\n// Profile picture placeholder\nSkeleton::new()\n    .w(px(64.))\n    .h(px(64.))\n    .rounded_full()\n```\n\n### Rectangle Skeleton\n\n```rust\n// Card image placeholder\nSkeleton::new()\n    .w(px(250.))\n    .h(px(125.))\n    .rounded_md()\n\n// Button placeholder\nSkeleton::new()\n    .w(px(120.))\n    .h(px(40.))\n    .rounded_md()\n```\n\n### Different Shapes\n\n```rust\n// Text content\nSkeleton::new().w(px(200.)).h_4().rounded_sm()\n\n// Square image\nSkeleton::new().size_20().rounded_md()\n\n// Wide banner\nSkeleton::new().w_full().h(px(200.)).rounded_lg()\n\n// Small icon\nSkeleton::new().size_6().rounded_md()\n```\n\n### Secondary Variant\n\n```rust\n// Use secondary color (more subtle)\nSkeleton::new()\n    .secondary()\n    .w(px(200.))\n    .h_4()\n    .rounded_md()\n```\n\n## Animation\n\nThe Skeleton component includes a built-in pulse animation that:\n\n- Runs continuously with a 2-second duration\n- Uses a bounce easing function with ease-in-out\n- Animates opacity from 100% to 50% and back\n- Automatically repeats to indicate loading state\n\nThe animation cannot be disabled as it's essential for indicating loading state.\n\n## Sizes\n\nThe Skeleton component doesn't have predefined size variants. Instead, use gpui's sizing utilities:\n\n```rust\n// Height utilities\nSkeleton::new().h_3()    // 12px height\nSkeleton::new().h_4()    // 16px height\nSkeleton::new().h_5()    // 20px height\nSkeleton::new().h_6()    // 24px height\n\n// Width utilities\nSkeleton::new().w(px(100.))   // 100px width\nSkeleton::new().w(px(200.))   // 200px width\nSkeleton::new().w_full()      // Full width\nSkeleton::new().w_1_2()       // 50% width\n\n// Square sizes\nSkeleton::new().size_4()      // 16x16px\nSkeleton::new().size_8()      // 32x32px\nSkeleton::new().size_12()     // 48x48px\nSkeleton::new().size_16()     // 64x64px\n```\n\n## Examples\n\n### Loading Profile Card\n\n```rust\nv_flex()\n    .gap_4()\n    .p_4()\n    .border_1()\n    .border_color(cx.theme().border)\n    .rounded(cx.theme().radius_lg)\n    .child(\n        h_flex()\n            .gap_3()\n            .items_center()\n            .child(Skeleton::new().size_12().rounded_full()) // Avatar\n            .child(\n                v_flex()\n                    .gap_2()\n                    .child(Skeleton::new().w(px(120.)).h_4().rounded_md()) // Name\n                    .child(Skeleton::new().w(px(100.)).h_3().rounded_md()) // Email\n            )\n    )\n    .child(\n        v_flex()\n            .gap_2()\n            .child(Skeleton::new().w_full().h_4().rounded_md()) // Bio line 1\n            .child(Skeleton::new().w(px(200.)).h_4().rounded_md()) // Bio line 2\n    )\n```\n\n### Loading Article List\n\n```rust\nv_flex()\n    .gap_6()\n    .children((0..3).map(|_| {\n        h_flex()\n            .gap_4()\n            .child(Skeleton::new().w(px(120.)).h(px(80.)).rounded_md()) // Thumbnail\n            .child(\n                v_flex()\n                    .gap_2()\n                    .flex_1()\n                    .child(Skeleton::new().w_full().h_5().rounded_md()) // Title\n                    .child(Skeleton::new().w(px(300.)).h_4().rounded_md()) // Excerpt line 1\n                    .child(Skeleton::new().w(px(250.)).h_4().rounded_md()) // Excerpt line 2\n                    .child(Skeleton::new().w(px(100.)).h_3().rounded_md()) // Date\n            )\n    }))\n```\n\n### Loading Table Rows\n\n```rust\nv_flex()\n    .gap_2()\n    .children((0..5).map(|_| {\n        h_flex()\n            .gap_4()\n            .p_3()\n            .border_b_1()\n            .border_color(cx.theme().border)\n            .child(Skeleton::new().size_8().rounded_full()) // Status indicator\n            .child(Skeleton::new().w(px(150.)).h_4().rounded_md()) // Name\n            .child(Skeleton::new().w(px(200.)).h_4().rounded_md()) // Email\n            .child(Skeleton::new().w(px(80.)).h_4().rounded_md()) // Role\n            .child(Skeleton::new().w(px(60.)).h_4().rounded_md()) // Actions\n    }))\n```\n\n### Loading Button States\n\n```rust\nh_flex()\n    .gap_3()\n    .child(Skeleton::new().w(px(80.)).h(px(36.)).rounded_md()) // Primary button\n    .child(Skeleton::new().w(px(70.)).h(px(36.)).rounded_md()) // Secondary button\n    .child(Skeleton::new().size_9().rounded_md()) // Icon button\n```\n\n### Loading Form Fields\n\n```rust\nv_flex()\n    .gap_4()\n    .child(\n        v_flex()\n            .gap_1()\n            .child(Skeleton::new().w(px(60.)).h_4().rounded_md()) // Label\n            .child(Skeleton::new().w_full().h(px(40.)).rounded_md()) // Input\n    )\n    .child(\n        v_flex()\n            .gap_1()\n            .child(Skeleton::new().w(px(80.)).h_4().rounded_md()) // Label\n            .child(Skeleton::new().w_full().h(px(120.)).rounded_md()) // Textarea\n    )\n```\n\n### Conditional Loading\n\n```rust\nif loading {\n    Skeleton::new().w(px(200.)).h_4().rounded_md()\n} else {\n    div().child(\"Actual content here\")\n}\n```\n\n## Theming\n\nThe Skeleton component uses the theme's `skeleton` color, which defaults to the `secondary` color if not specified. You can customize it in your theme:\n\n```json\n{\n  \"skeleton.background\": \"#e2e8f0\"\n}\n```\n\nThe `secondary(true)` variant applies 50% opacity to the skeleton color for more subtle loading indicators.\n"
  },
  {
    "path": "docs/docs/components/slider.md",
    "content": "---\ntitle: Slider\ndescription: A control that allows the user to select values from a range using a draggable thumb.\n---\n\n# Slider\n\nA slider component for selecting numeric values within a specified range. Supports both single value and range selection modes, horizontal and vertical orientations, custom styling, and step intervals.\n\n## Import\n\n```rust\nuse gpui_component::slider::{Slider, SliderState, SliderEvent, SliderValue};\n```\n\n## Usage\n\n### Basic Slider\n\n```rust\nlet slider_state = cx.new(|_| {\n    SliderState::new()\n        .min(0.0)\n        .max(100.0)\n        .default_value(50.0)\n        .step(1.0)\n});\n\nSlider::new(&slider_state)\n```\n\n### Slider with Event Handling\n\n```rust\nstruct MyView {\n    slider_state: Entity<SliderState>,\n    current_value: f32,\n}\n\nimpl MyView {\n    fn new(cx: &mut Context<Self>) -> Self {\n        let slider_state = cx.new(|_| {\n            SliderState::new()\n                .min(0.0)\n                .max(100.0)\n                .default_value(25.0)\n                .step(5.0)\n        });\n\n        let subscription = cx.subscribe(&slider_state, |this, _, event: &SliderEvent, cx| {\n            match event {\n                SliderEvent::Change(value) => {\n                    this.current_value = value.start();\n                    cx.notify();\n                }\n            }\n        });\n\n        Self {\n            slider_state,\n            current_value: 25.0,\n        }\n    }\n}\n\nimpl Render for MyView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_2()\n            .child(Slider::new(&self.slider_state))\n            .child(format!(\"Value: {}\", self.current_value))\n    }\n}\n```\n\n### Range Slider\n\n```rust\nlet range_slider = cx.new(|_| {\n    SliderState::new()\n        .min(0.0)\n        .max(100.0)\n        .default_value(20.0..80.0)  // Range from 20 to 80\n        .step(1.0)\n});\n\nSlider::new(&range_slider)\n```\n\n### Vertical Slider\n\n```rust\nSlider::new(&slider_state)\n    .vertical()\n    .h(px(200.))\n```\n\n### Custom Step Intervals\n\n```rust\n// Integer steps\nlet integer_slider = cx.new(|_| {\n    SliderState::new()\n        .min(0.0)\n        .max(10.0)\n        .step(1.0)\n        .default_value(5.0)\n});\n\n// Decimal steps\nlet decimal_slider = cx.new(|_| {\n    SliderState::new()\n        .min(0.0)\n        .max(1.0)\n        .step(0.01)\n        .default_value(0.5)\n});\n```\n\n### Min/Max Configuration\n\n```rust\n// Temperature slider\nlet temp_slider = cx.new(|_| {\n    SliderState::new()\n        .min(-10.0)\n        .max(40.0)\n        .default_value(20.0)\n        .step(0.5)\n});\n\n// Percentage slider\nlet percent_slider = cx.new(|_| {\n    SliderState::new()\n        .min(0.0)\n        .max(100.0)\n        .default_value(75.0)\n        .step(5.0)\n});\n```\n\n### Disabled State\n\n```rust\nSlider::new(&slider_state)\n    .disabled(true)\n```\n\n### Custom Styling\n\n```rust\nSlider::new(&slider_state)\n    .bg(cx.theme().success)\n    .text_color(cx.theme().success_foreground)\n    .rounded(px(4.))\n```\n\n### Scale\n\nThere have 2 types of scale for the slider:\n\n- `Linear` (default)\n- `Logarithmic`\n\nThe logarithmic scale is useful when the range of values is large and you want to give more precision to smaller values.\n\n```rust\nlet log_slider = cx.new(|_| {\n    SliderState::new()\n        .min(1.0) // min must be greater than 0 for log scale\n        .max(1000.0)\n        .default_value(10.0)\n        .step(1.0)\n        .scale(SliderScale::Logarithmic)\n});\n```\n\nIn this case:\n\n:::info\n$$ v = min \\times (max/min)^p $$\n\nThe value `v` is calculated using the formula above, where `p` is the slider percentage (0 to 1).\n:::\n\n- If slider at 25%, value will be approximately `5.62`.\n- If slider at 50%, value will be approximately `31.62`.\n- If slider at 75%, value will be approximately `177.83`.\n- If slider at 100%, value will be `1000.0`.\n\n#### Conversions\n\n```rust\n// From f32\nlet single_value: SliderValue = 42.0.into();\n\n// From tuple\nlet range_value: SliderValue = (10.0, 90.0).into();\n\n// From Range\nlet range_value: SliderValue = (10.0..90.0).into();\n```\n\n### SliderEvent\n\n| Event                 | Description                       |\n| --------------------- | --------------------------------- |\n| `Change(SliderValue)` | Emitted when slider value changes |\n\n### Styling\n\nThe slider component implements `Styled` trait and supports:\n\n- Background color for track and thumb\n- Text color for thumb\n- Border radius\n- Size customization\n\n## Examples\n\n### Color Picker\n\n```rust\nstruct ColorPicker {\n    hue_slider: Entity<SliderState>,\n    saturation_slider: Entity<SliderState>,\n    lightness_slider: Entity<SliderState>,\n    alpha_slider: Entity<SliderState>,\n    current_color: Hsla,\n}\n\nimpl ColorPicker {\n    fn new(cx: &mut Context<Self>) -> Self {\n        let hue_slider = cx.new(|_| {\n            SliderState::new()\n                .min(0.0)\n                .max(1.0)\n                .step(0.01)\n                .default_value(0.5)\n        });\n\n        let saturation_slider = cx.new(|_| {\n            SliderState::new()\n                .min(0.0)\n                .max(1.0)\n                .step(0.01)\n                .default_value(1.0)\n        });\n\n        // Subscribe to all sliders to update color\n        let subscriptions = [&hue_slider, &saturation_slider /* ... */]\n            .iter()\n            .map(|slider| {\n                cx.subscribe(slider, |this, _, event: &SliderEvent, cx| {\n                    match event {\n                        SliderEvent::Change(_) => {\n                            this.update_color(cx);\n                        }\n                    }\n                })\n            })\n            .collect::<Vec<_>>();\n\n        Self {\n            hue_slider,\n            saturation_slider,\n            // ... other fields\n        }\n    }\n\n    fn update_color(&mut self, cx: &mut Context<Self>) {\n        let h = self.hue_slider.read(cx).value().start();\n        let s = self.saturation_slider.read(cx).value().start();\n        // ... calculate color\n        self.current_color = hsla(h, s, l, a);\n        cx.notify();\n    }\n}\n\nimpl Render for ColorPicker {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        h_flex()\n            .gap_4()\n            .child(\n                v_flex()\n                    .gap_2()\n                    .child(\"Hue\")\n                    .child(Slider::new(&self.hue_slider).vertical().h(px(120.)))\n            )\n            .child(\n                v_flex()\n                    .gap_2()\n                    .child(\"Saturation\")\n                    .child(Slider::new(&self.saturation_slider).vertical().h(px(120.)))\n            )\n            // ... other sliders\n    }\n}\n```\n\n### Volume Control\n\n```rust\nstruct VolumeControl {\n    volume_slider: Entity<SliderState>,\n    volume: f32,\n}\n\nimpl VolumeControl {\n    fn new(cx: &mut Context<Self>) -> Self {\n        let volume_slider = cx.new(|_| {\n            SliderState::new()\n                .min(0.0)\n                .max(100.0)\n                .step(1.0)\n                .default_value(50.0)\n        });\n\n        let subscription = cx.subscribe(&volume_slider, |this, _, event: &SliderEvent, cx| {\n            match event {\n                SliderEvent::Change(value) => {\n                    this.volume = value.start();\n                    this.apply_volume_change();\n                    cx.notify();\n                }\n            }\n        });\n\n        Self {\n            volume_slider,\n            volume: 50.0,\n        }\n    }\n\n    fn apply_volume_change(&self) {\n        // Apply volume change to audio system\n        println!(\"Volume changed to: {}%\", self.volume);\n    }\n}\n\nimpl Render for VolumeControl {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        h_flex()\n            .items_center()\n            .gap_3()\n            .child(\"🔊\")\n            .child(Slider::new(&self.volume_slider).flex_1())\n            .child(format!(\"{}%\", self.volume as i32))\n    }\n}\n```\n\n### Price Range Filter\n\n```rust\nstruct PriceFilter {\n    price_range: Entity<SliderState>,\n    min_price: f32,\n    max_price: f32,\n}\n\nimpl PriceFilter {\n    fn new(cx: &mut Context<Self>) -> Self {\n        let price_range = cx.new(|_| {\n            SliderState::new()\n                .min(0.0)\n                .max(1000.0)\n                .step(10.0)\n                .default_value(100.0..500.0)  // Range slider\n        });\n\n        let subscription = cx.subscribe(&price_range, |this, _, event: &SliderEvent, cx| {\n            match event {\n                SliderEvent::Change(value) => {\n                    this.min_price = value.start();\n                    this.max_price = value.end();\n                    this.filter_products();\n                    cx.notify();\n                }\n            }\n        });\n\n        Self {\n            price_range,\n            min_price: 100.0,\n            max_price: 500.0,\n        }\n    }\n\n    fn filter_products(&self) {\n        println!(\"Filtering products: ${} - ${}\", self.min_price, self.max_price);\n    }\n}\n\nimpl Render for PriceFilter {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_2()\n            .child(\"Price Range\")\n            .child(Slider::new(&self.price_range))\n            .child(format!(\"${} - ${}\", self.min_price as i32, self.max_price as i32))\n    }\n}\n```\n\n### Temperature Slider with Custom Styling\n\n```rust\nstruct TemperatureControl {\n    temp_slider: Entity<SliderState>,\n    temperature: f32,\n}\n\nimpl Render for TemperatureControl {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let temp_color = if self.temperature < 10.0 {\n            cx.theme().info  // Cold - blue\n        } else if self.temperature > 25.0 {\n            cx.theme().destructive  // Hot - red\n        } else {\n            cx.theme().success  // Comfortable - green\n        };\n\n        v_flex()\n            .gap_3()\n            .child(\"Temperature Control\")\n            .child(\n                Slider::new(&self.temp_slider)\n                    .bg(temp_color)\n                    .text_color(cx.theme().background)\n                    .rounded(px(8.))\n            )\n            .child(format!(\"{}°C\", self.temperature as i32))\n    }\n}\n```\n\n## Keyboard Shortcuts\n\n| Key           | Action                         |\n| ------------- | ------------------------------ |\n| `←` / `↓`     | Decrease value by step         |\n| `→` / `↑`     | Increase value by step         |\n| `Page Down`   | Decrease by larger amount      |\n| `Page Up`     | Increase by larger amount      |\n| `Home`        | Set to minimum value           |\n| `End`         | Set to maximum value           |\n| `Tab`         | Move focus to next element     |\n| `Shift + Tab` | Move focus to previous element |\n"
  },
  {
    "path": "docs/docs/components/spinner.md",
    "content": "---\ntitle: Spinner\ndescription: Displays an animated loading showing the completion progress of a task.\n---\n\n# Spinner\n\nSpinner element displays an animated loading. Perfect for showing loading states, progress spinners, and other visual feedback during asynchronous operations. Features customizable icons, colors, sizes, and rotation animations.\n\n## Import\n\n```rust\nuse gpui_component::spinner::Spinner;\n```\n\n## Usage\n\n### Basic\n\n```rust\n// Default loader icon\nSpinner::new()\n```\n\n### Spinner with Custom Color\n\n```rust\nuse gpui_component::ActiveTheme;\n\n// Blue spinner\nSpinner::new()\n    .color(cx.theme().blue)\n\n// Green spinner for success states\nSpinner::new()\n    .color(cx.theme().green)\n\n// Custom color\nSpinner::new()\n    .color(cx.theme().cyan)\n```\n\n### Spinner Sizes\n\n```rust\n// Extra small spinner\nSpinner::new().xsmall()\n\n// Small spinner\nSpinner::new().small()\n\n// Medium spinner (default)\nSpinner::new()\n\n// Large spinner\nSpinner::new().large()\n\n// Custom size\nSpinner::new().with_size(px(64.))\n```\n\n### Spinner with Custom Icon\n\n```rust\nuse gpui_component::IconName;\n\n// Loading circle icon\nSpinner::new()\n    .icon(IconName::LoaderCircle)\n\n// Large loading circle with custom color\nSpinner::new()\n    .icon(IconName::LoaderCircle)\n    .large()\n    .color(cx.theme().cyan)\n\n// Different loading icons\nSpinner::new()\n    .icon(IconName::Loader)\n    .color(cx.theme().primary)\n```\n\n## Available Icons\n\nThe Spinner component supports various loading and progress icons:\n\n### Loading Icons\n\n- `Loader` (default) - Rotating line spinner\n- `LoaderCircle` - Circular loading spinner\n\n### Other Compatible Icons\n\n- Any icon from the `IconName` enum can be used, though loading-specific icons work best with the rotation animation\n\n## Animation\n\nThe Spinner component features a built-in rotation animation:\n\n- **Duration**: 0.8 seconds (configurable via speed parameter)\n- **Easing**: Ease-in-out transition\n- **Repeat**: Infinite loop\n- **Transform**: 360-degree rotation\n\n## Size Reference\n\n| Size        | Method              | Approximate Pixels |\n| ----------- | ------------------- | ------------------ |\n| Extra Small | `.xsmall()`         | ~12px              |\n| Small       | `.small()`          | ~14px              |\n| Medium      | (default)           | ~16px              |\n| Large       | `.large()`          | ~24px              |\n| Custom      | `.with_size(px(n))` | n px               |\n\n## Examples\n\n### Loading States\n\n```rust\n// Simple loading spinner\nSpinner::new()\n\n// Loading with custom color\nSpinner::new()\n    .color(cx.theme().blue)\n\n// Large loading spinner\nSpinner::new()\n    .large()\n    .color(cx.theme().primary)\n```\n\n### Different Loading Icons\n\n```rust\n// Default loader (line spinner)\nSpinner::new()\n    .color(cx.theme().muted_foreground)\n\n// Circle loader\nSpinner::new()\n    .icon(IconName::LoaderCircle)\n    .color(cx.theme().blue)\n\n// Large circle loader with custom color\nSpinner::new()\n    .icon(IconName::LoaderCircle)\n    .large()\n    .color(cx.theme().green)\n```\n\n### Status Spinners\n\n```rust\n// Loading state\nSpinner::new()\n    .small()\n    .color(cx.theme().muted_foreground)\n\n// Processing state\nSpinner::new()\n    .icon(IconName::LoaderCircle)\n    .color(cx.theme().blue)\n\n// Success processing (still animating)\nSpinner::new()\n    .icon(IconName::LoaderCircle)\n    .color(cx.theme().green)\n```\n\n### Size Variations\n\n```rust\n// Extra small for inline text\nSpinner::new()\n    .xsmall()\n    .color(cx.theme().muted_foreground)\n\n// Small for buttons\nSpinner::new()\n    .small()\n    .color(cx.theme().primary_foreground)\n\n// Medium for general use (default)\nSpinner::new()\n    .color(cx.theme().primary)\n\n// Large for prominent loading states\nSpinner::new()\n    .large()\n    .color(cx.theme().blue)\n\n// Custom size for specific requirements\nSpinner::new()\n    .with_size(px(32.))\n    .color(cx.theme().orange)\n```\n\n### In UI Components\n\n```rust\n// In a button\nButton::new(\"submit-btn\")\n    .loading(true)\n    .icon(\n        Spinner::new()\n            .small()\n            .color(cx.theme().primary_foreground)\n    )\n    .label(\"Loading...\")\n\n// In a card header\ndiv()\n    .flex()\n    .items_center()\n    .gap_2()\n    .child(\"Processing...\")\n    .child(\n        Spinner::new()\n            .small()\n            .color(cx.theme().muted_foreground)\n    )\n\n// Full-screen loading\ndiv()\n    .flex()\n    .items_center()\n    .justify_center()\n    .h_full()\n    .w_full()\n    .child(\n        Spinner::new()\n            .large()\n            .color(cx.theme().primary)\n    )\n```\n\n## Performance Considerations\n\n- The animation uses CSS transforms for optimal performance\n- Multiple spinners on the same page share the same animation timing\n- The component is lightweight and suitable for frequent updates\n- Consider using smaller sizes for better performance with many spinners\n\n## Common Patterns\n\n### Conditional Loading\n\n```rust\n// Show spinner only when loading\n.when(is_loading, |this| {\n    this.child(\n        Spinner::new()\n            .small()\n            .color(cx.theme().muted_foreground)\n    )\n})\n```\n\n### Loading with Text\n\n```rust\n// Loading text with spinner\nh_flex()\n    .items_center()\n    .gap_2()\n    .child(\n        Spinner::new()\n            .small()\n            .color(cx.theme().primary)\n    )\n    .child(\"Loading data...\")\n```\n\n### Overlay Loading\n\n```rust\n// Full overlay with spinner\ndiv()\n    .absolute()\n    .inset_0()\n    .flex()\n    .items_center()\n    .justify_center()\n    .bg(cx.theme().background.alpha(0.8))\n    .child(\n        v_flex()\n            .items_center()\n            .gap_3()\n            .child(\n                Spinner::new()\n                    .large()\n                    .color(cx.theme().primary)\n            )\n            .child(\"Loading...\")\n    )\n```\n"
  },
  {
    "path": "docs/docs/components/stepper.md",
    "content": "---\ntitle: Stepper\ndescription: A step-by-step progress for users to navigate through a series of steps or stages.\n---\n\n# Stepper\n\nA step-by-step progress component that guides users through a series of steps or stages. Supports horizontal and vertical layouts, custom icons, and different sizes.\n\n## Import\n\n```rust\nuse gpui_component::stepper::{Stepper, StepperItem};\n```\n\n## Usage\n\n### Basic Stepper\n\nUse `selected_index` method to set current active step by index (0-based), default is `0`.\n\n```rust\nStepper::new(\"my-stepper\")\n    .selected_index(0)\n    .items([\n        StepperItem::new().child(\"Step 1\"),\n        StepperItem::new().child(\"Step 2\"),\n        StepperItem::new().child(\"Step 3\"),\n    ])\n    .on_click(|step, _, _| {\n        println!(\"Clicked step: {}\", step);\n    })\n```\n\n### With Icons\n\n```rust\nuse gpui_component::IconName;\n\nStepper::new(\"icon-stepper\")\n    .selected_index(0)\n    .items([\n        StepperItem::new()\n            .icon(IconName::Calendar)\n            .child(\"Order Details\"),\n        StepperItem::new()\n            .icon(IconName::Inbox)\n            .child(\"Shipping\"),\n        StepperItem::new()\n            .icon(IconName::Frame)\n            .child(\"Preview\"),\n        StepperItem::new()\n            .icon(IconName::Info)\n            .child(\"Finish\"),\n    ])\n```\n\n### Vertical Layout\n\n```rust\nStepper::new(\"vertical-stepper\")\n    .vertical()\n    .selected_index(2)\n    .items_center()\n    .items([\n        StepperItem::new()\n            .pb_8()\n            .icon(IconName::Building2)\n            .child(v_flex().child(\"Step 1\").child(\"Description for step 1.\")),\n        StepperItem::new()\n            .pb_8()\n            .icon(IconName::Asterisk)\n            .child(v_flex().child(\"Step 2\").child(\"Description for step 2.\")),\n        StepperItem::new()\n            .pb_8()\n            .icon(IconName::Folder)\n            .child(v_flex().child(\"Step 3\").child(\"Description for step 3.\")),\n        StepperItem::new()\n            .icon(IconName::CircleCheck)\n            .child(v_flex().child(\"Step 4\").child(\"Description for step 4.\")),\n    ])\n```\n\n### Text Center\n\nThe `text_center` method centers the text within each step item.\n\n```rust\nStepper::new(\"center-stepper\")\n    .selected_index(0)\n    .text_center(true)\n    .items([\n        StepperItem::new().child(\n            v_flex()\n                .items_center()\n                .child(\"Step 1\")\n                .child(\"Desc for step 1.\"),\n        ),\n        StepperItem::new().child(\n            v_flex()\n                .items_center()\n                .child(\"Step 2\")\n                .child(\"Desc for step 2.\"),\n        ),\n        StepperItem::new().child(\n            v_flex()\n                .items_center()\n                .child(\"Step 3\")\n                .child(\"Desc for step 3.\"),\n        ),\n    ])\n```\n\n### Different Sizes\n\n```rust\nuse gpui_component::{Sizable as _, Size};\n\nStepper::new(\"stepper\")\n    .xsmall()\n    .items([...])\n\nStepper::new(\"stepper\")\n    .small()\n    .items([...])\n\nStepper::new(\"stepper\")\n    .large()\n    .items([...])\n```\n\n### Disabled State\n\n```rust\nStepper::new(\"disabled-stepper\")\n    .disabled(true)\n    .items([\n        StepperItem::new().child(\"Step 1\"),\n        StepperItem::new().child(\"Step 2\"),\n    ])\n```\n\n### Handle Click Events\n\n```rust\nStepper::new(\"my-stepper\")\n    .selected_index(current_step)\n    .items([\n        StepperItem::new().child(\"Step 1\"),\n        StepperItem::new().child(\"Step 2\"),\n        StepperItem::new().child(\"Step 3\"),\n    ])\n    .on_click(cx.listener(|this, step, _, cx| {\n        this.current_step = *step;\n        cx.notify();\n    }))\n```\n\n## API Reference\n\n- [Stepper]\n- [StepperItem]\n\n### Sizing\n\nImplements [Sizable] trait:\n\n- `xsmall()` - Extra small size\n- `small()` - Small size\n- `medium()` - Medium size (default)\n- `large()` - Large size\n\n## Examples\n\n### Multi-step Form\n\n```rust\nStepper::new(\"form-stepper\")\n    .w_full()\n    .selected_index(form_step)\n    .items([\n        StepperItem::new()\n            .icon(IconName::User)\n            .child(\"Personal Info\"),\n        StepperItem::new()\n            .icon(IconName::CreditCard)\n            .child(\"Payment\"),\n        StepperItem::new()\n            .icon(IconName::CircleCheck)\n            .child(\"Confirmation\"),\n    ])\n    .on_click(cx.listener(|this, step, _, cx| {\n        this.form_step = *step;\n        cx.notify();\n    }))\n```\n\n### Disabled Individual Steps\n\n```rust\nStepper::new(\"stepper\")\n    .selected_index(0)\n    .items([\n        StepperItem::new().child(\"Available\"),\n        StepperItem::new().disabled(true).child(\"Locked\"),\n        StepperItem::new().child(\"Available\"),\n    ])\n```\n\n[Stepper]: https://docs.rs/gpui-component/latest/gpui_component/stepper/struct.Stepper.html\n[StepperItem]: https://docs.rs/gpui-component/latest/gpui_component/stepper/struct.StepperItem.html\n[Sizable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Sizable.html\n"
  },
  {
    "path": "docs/docs/components/switch.md",
    "content": "---\ntitle: Switch\ndescription: A control that allows the user to toggle between checked and not checked.\n---\n\n# Switch\n\nA toggle switch component for binary on/off states. Features smooth animations, different sizes, labels, disabled state, and customizable positioning.\n\n## Import\n\n```rust\nuse gpui_component::switch::Switch;\n```\n\n## Usage\n\n### Basic Switch\n\n```rust\nSwitch::new(\"my-switch\")\n    .checked(false)\n    .on_click(|checked, _, _| {\n        println!(\"Switch is now: {}\", checked);\n    })\n```\n\n### Controlled Switch\n\n```rust\nstruct MyView {\n    is_enabled: bool,\n}\n\nimpl Render for MyView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        Switch::new(\"switch\")\n            .checked(self.is_enabled)\n            .on_click(cx.listener(|view, checked, _, cx| {\n                view.is_enabled = *checked;\n                cx.notify();\n            }))\n    }\n}\n```\n\n### With Label\n\n```rust\nSwitch::new(\"notifications\")\n    .label(\"Enable notifications\")\n    .checked(true)\n    .on_click(|checked, _, _| {\n        println!(\"Notifications: {}\", if *checked { \"enabled\" } else { \"disabled\" });\n    })\n```\n\n### Different Sizes\n\n```rust\n// Small switch\nSwitch::new(\"small-switch\")\n    .small()\n    .label(\"Small switch\")\n\n// Medium switch (default)\nSwitch::new(\"medium-switch\")\n    .label(\"Medium switch\")\n\n// Using explicit size\nSwitch::new(\"custom-switch\")\n    .with_size(Size::Small)\n    .label(\"Custom size\")\n```\n\n### Disabled State\n\n```rust\n// Disabled unchecked\nSwitch::new(\"disabled-off\")\n    .label(\"Disabled (off)\")\n    .disabled(true)\n    .checked(false)\n\n// Disabled checked\nSwitch::new(\"disabled-on\")\n    .label(\"Disabled (on)\")\n    .disabled(true)\n    .checked(true)\n```\n\n### With Tooltip\n\n```rust\nSwitch::new(\"switch\")\n    .label(\"Airplane mode\")\n    .tooltip(\"Enable airplane mode to disable all wireless connections\")\n    .checked(false)\n```\n\n## API Reference\n\n### Switch\n\n| Method             | Description                                                 |\n| ------------------ | ----------------------------------------------------------- |\n| `new(id)`          | Create a new switch with the given ID                       |\n| `checked(bool)`    | Set the checked/toggled state                               |\n| `label(text)`      | Set label text for the switch                               |\n| `label_side(side)` | Position label (Side::Left or Side::Right)                  |\n| `disabled(bool)`   | Set disabled state                                          |\n| `tooltip(text)`    | Add tooltip text                                            |\n| `on_click(fn)`     | Callback when clicked, receives `&bool` (new checked state) |\n\n### Styling\n\nImplements `Sizable` and `Disableable` traits:\n\n- `small()` - Small switch size (28x16px toggle area)\n- `medium()` - Medium switch size (36x20px toggle area, default)\n- `with_size(size)` - Set explicit size\n- `disabled(bool)` - Disabled state\n\n### Styling Properties\n\nThe switch can also be styled using GPUI's styling methods:\n\n- `w(width)` - Custom width\n- `h(height)` - Custom height\n- Standard margin, padding, and positioning methods\n\n## Examples\n\n### Settings Panel\n\n```rust\nstruct SettingsView {\n    marketing_emails: bool,\n    security_emails: bool,\n    push_notifications: bool,\n}\n\nv_flex()\n    .gap_4()\n    .child(\n        // Setting with description\n        v_flex()\n            .gap_2()\n            .child(\n                h_flex()\n                    .items_center()\n                    .justify_between()\n                    .child(\n                        v_flex()\n                            .child(Label::new(\"Marketing emails\").text_lg())\n                            .child(\n                                Label::new(\"Receive emails about new products and features\")\n                                    .text_color(theme.muted_foreground)\n                            )\n                    )\n                    .child(\n                        Switch::new(\"marketing\")\n                            .checked(self.marketing_emails)\n                            .on_click(cx.listener(|view, checked, _, cx| {\n                                view.marketing_emails = *checked;\n                                cx.notify();\n                            }))\n                    )\n            )\n    )\n    .child(\n        // Simple setting\n        h_flex()\n            .items_center()\n            .justify_between()\n            .child(Label::new(\"Push notifications\"))\n            .child(\n                Switch::new(\"push\")\n                    .checked(self.push_notifications)\n                    .on_click(cx.listener(|view, checked, _, cx| {\n                        view.push_notifications = *checked;\n                        cx.notify();\n                    }))\n            )\n    )\n```\n\n### Compact Settings List\n\n```rust\nv_flex()\n    .gap_3()\n    .child(\n        Switch::new(\"wifi\")\n            .label(\"Wi-Fi\")\n            .label_side(Side::Left)\n            .checked(true)\n            .small()\n    )\n    .child(\n        Switch::new(\"bluetooth\")\n            .label(\"Bluetooth\")\n            .label_side(Side::Left)\n            .checked(false)\n            .small()\n    )\n    .child(\n        Switch::new(\"airplane\")\n            .label(\"Airplane Mode\")\n            .label_side(Side::Left)\n            .checked(false)\n            .disabled(true)\n            .small()\n    )\n```\n\n### Form Integration\n\n```rust\nstruct FormData {\n    subscribe_newsletter: bool,\n    enable_notifications: bool,\n    remember_me: bool,\n}\n\nv_flex()\n    .gap_4()\n    .p_4()\n    .border_1()\n    .border_color(theme.border)\n    .rounded(theme.radius)\n    .child(\n        Switch::new(\"newsletter\")\n            .label(\"Subscribe to newsletter\")\n            .checked(self.subscribe_newsletter)\n            .tooltip(\"Receive monthly updates about new features\")\n            .on_click(cx.listener(|view, checked, _, cx| {\n                view.subscribe_newsletter = *checked;\n                cx.notify();\n            }))\n    )\n    .child(\n        Switch::new(\"notifications\")\n            .label(\"Enable notifications\")\n            .checked(self.enable_notifications)\n            .on_click(cx.listener(|view, checked, _, cx| {\n                view.enable_notifications = *checked;\n                cx.notify();\n            }))\n    )\n    .child(\n        Switch::new(\"remember\")\n            .label(\"Remember me\")\n            .checked(self.remember_me)\n            .small()\n            .on_click(cx.listener(|view, checked, _, cx| {\n                view.remember_me = *checked;\n                cx.notify();\n            }))\n    )\n```\n\n### Custom Styling\n\n```rust\nSwitch::new(\"custom\")\n    .label(\"Custom styled switch\")\n    .w(px(200.))\n    .checked(true)\n    .on_click(|checked, _, _| {\n        println!(\"Custom switch: {}\", checked);\n    })\n```\n\n## Animation\n\nThe switch features smooth animations:\n\n- **Toggle animation**: 150ms duration when switching states\n- **Background color transition**: Changes from switch color to primary color\n- **Position animation**: Smooth movement of the toggle indicator\n- **Disabled state**: Animations are disabled when the switch is disabled\n"
  },
  {
    "path": "docs/docs/components/table.md",
    "content": "---\ntitle: Table\ndescription: A basic table component for directly rendering tabular data.\n---\n\n# Table\n\nA simple, stateless, composable table component for rendering tabular data. Unlike [DataTable], this component does not include virtual scrolling, sorting, or column management — it is designed for straightforward data display using a declarative API.\n\n## Import\n\n```rust\nuse gpui_component::table::{\n    Table, TableHeader, TableBody, TableFooter,\n    TableRow, TableHead, TableCell, TableCaption,\n};\n```\n\n## Usage\n\n### Basic Table\n\n```rust\nTable::new()\n    .child(TableHeader::new().child(\n        TableRow::new()\n            .child(TableHead::new().child(\"Name\"))\n            .child(TableHead::new().child(\"Email\"))\n            .child(TableHead::new().text_right().child(\"Amount\"))\n    ))\n    .child(TableBody::new()\n        .child(TableRow::new()\n            .child(TableCell::new().child(\"John\"))\n            .child(TableCell::new().child(\"john@example.com\"))\n            .child(TableCell::new().text_right().child(\"$100.00\")))\n        .child(TableRow::new()\n            .child(TableCell::new().child(\"Jane\"))\n            .child(TableCell::new().child(\"jane@example.com\"))\n            .child(TableCell::new().text_right().child(\"$200.00\")))\n    )\n    .child(TableCaption::new().child(\"A list of recent invoices.\"))\n```\n\n### With Footer\n\n```rust\nTable::new()\n    .child(TableHeader::new().child(\n        TableRow::new()\n            .child(TableHead::new().child(\"Invoice\"))\n            .child(TableHead::new().child(\"Status\"))\n            .child(TableHead::new().text_right().child(\"Amount\"))\n    ))\n    .child(TableBody::new()\n        .child(TableRow::new()\n            .child(TableCell::new().child(\"INV001\"))\n            .child(TableCell::new().child(\"Paid\"))\n            .child(TableCell::new().text_right().child(\"$250.00\")))\n    )\n    .child(TableFooter::new().child(\n        TableRow::new()\n            .child(TableCell::new().child(\"Total\"))\n            .child(TableCell::new().child(\"\"))\n            .child(TableCell::new().text_right().child(\"$250.00\"))\n    ))\n```\n\n### Column Widths\n\nUse `.w()` on `TableHead` and `TableCell` to set fixed column widths:\n\n```rust\nTableRow::new()\n    .child(TableHead::new().w(px(80.)).child(\"ID\"))\n    .child(TableHead::new().child(\"Name\"))  // flex-1\n    .child(TableHead::new().w(px(120.)).child(\"Date\"))\n```\n\n### Text Alignment\n\n```rust\n// Center-aligned header\nTableHead::new().text_center().child(\"Status\")\n\n// Right-aligned cell (e.g., for numbers)\nTableCell::new().text_right().child(\"$1,000.00\")\n```\n\n### Without Border (via Styled)\n\nAll table sub-components implement the `Styled` trait, so you can customize styles directly:\n\n```rust\n// Remove border and rounded corners\nTable::new()\n    .border_0()\n    .rounded_none()\n    .child(/* ... */)\n```\n\n### Custom Styling\n\nSince all components implement `Styled`, you can apply any GPUI style:\n\n```rust\n// Custom row hover\nTableRow::new()\n    .bg(cx.theme().table_even)\n    .child(/* ... */)\n\n// Custom cell padding\nTableCell::new()\n    .px_4()\n    .child(\"Custom padded content\")\n```\n\n## Sub-components\n\n| Component | Description |\n|-----------|-------------|\n| `Table` | Root container with border, rounded corners, and background |\n| `TableHeader` | Header section with distinct background and font weight |\n| `TableBody` | Body section wrapping data rows |\n| `TableFooter` | Footer section with top border |\n| `TableRow` | A flex row with bottom border |\n| `TableHead` | Header cell with alignment and width options |\n| `TableCell` | Data cell with alignment and width options |\n| `TableCaption` | Caption text below the table |\n\n## API Reference\n\n### Table\n\n- `new()` - Create a new table\n- Implements `Styled`, `ParentElement`, `Sizable`, `RenderOnce`\n\n### TableHead / TableCell\n\n- `new()` - Create a new head/cell\n- `w(width)` - Set fixed width (otherwise flex-1)\n- `text_center()` - Center-align content\n- `text_right()` - Right-align content\n- Implements `Styled`, `ParentElement`, `RenderOnce`\n\n### TableHeader / TableBody / TableFooter / TableRow / TableCaption\n\n- `new()` - Create a new instance\n- Implements `Styled`, `ParentElement`, `RenderOnce`\n\n## Table vs DataTable\n\n| Feature | Table | DataTable |\n|---------|-------|-----------|\n| Virtual scrolling | No | Yes |\n| Column sorting | No | Yes |\n| Column resizing | No | Yes |\n| Column moving | No | Yes |\n| Cell selection | No | Yes |\n| Row selection | No | Yes |\n| Infinite loading | No | Yes |\n| Keyboard navigation | No | Yes |\n| State management | Stateless | TableState |\n| Best for | Small, static data | Large, interactive datasets |\n\n[DataTable]: ./data-table.md\n"
  },
  {
    "path": "docs/docs/components/tabs.md",
    "content": "---\ntitle: Tabs\ndescription: A set of layered sections of content—known as tab panels—that are displayed one at a time.\n---\n\n# Tabs\n\nA tabbed interface component for organizing content into separate sections. Supports multiple variants, sizes, navigation controls, and interactive features like reordering and prefix/suffix elements.\n\n## Import\n\n```rust\nuse gpui_component::tab::{Tab, TabBar};\n```\n\n## Usage\n\n### Basic Tabs\n\n```rust\nTabBar::new(\"tabs\")\n    .selected_index(0)\n    .on_click(|selected_index, _, _| {\n        println!(\"Tab {} selected\", selected_index);\n    })\n    .child(Tab::new().label(\"Account\"))\n    .child(Tab::new().label(\"Profile\"))\n    .child(Tab::new().label(\"Settings\"))\n```\n\n### Tab Variants\n\n#### Default Tabs\n\n```rust\nTabBar::new(\"default-tabs\")\n    .selected_index(0)\n    .child(Tab::new().label(\"Account\"))\n    .child(Tab::new().label(\"Profile\"))\n    .child(Tab::new().label(\"Documents\"))\n```\n\n#### Underline Tabs\n\n```rust\nTabBar::new(\"underline-tabs\")\n    .underline()\n    .selected_index(0)\n    .child(Tab::new().label(\"Account\"))\n    .child(Tab::new().label(\"Profile\"))\n    .child(Tab::new().label(\"Documents\"))\n```\n\n#### Pill Tabs\n\n```rust\nTabBar::new(\"pill-tabs\")\n    .pill()\n    .selected_index(0)\n    .child(Tab::new().label(\"Account\"))\n    .child(Tab::new().label(\"Profile\"))\n    .child(Tab::new().label(\"Documents\"))\n```\n\n#### Outline Tabs\n\n```rust\nTabBar::new(\"outline-tabs\")\n    .outline()\n    .selected_index(0)\n    .child(Tab::new().label(\"Account\"))\n    .child(Tab::new().label(\"Profile\"))\n    .child(Tab::new().label(\"Documents\"))\n```\n\n#### Segmented Tabs\n\n```rust\nuse gpui_component::IconName;\n\nTabBar::new(\"segmented-tabs\")\n    .segmented()\n    .selected_index(0)\n    .child(IconName::Bot)\n    .child(IconName::Calendar)\n    .child(IconName::Map)\n    .children(vec![\"Settings\", \"About\"])\n```\n\n### Tab Sizes\n\n```rust\n// Extra Small\nTabBar::new(\"tabs\").xsmall()\n    .child(Tab::new().label(\"Small\"))\n\n// Small\nTabBar::new(\"tabs\").small()\n    .child(Tab::new().label(\"Small\"))\n\n// Medium (default)\nTabBar::new(\"tabs\")\n    .child(Tab::new().label(\"Medium\"))\n\n// Large\nTabBar::new(\"tabs\").large()\n    .child(Tab::new().label(\"Large\"))\n```\n\n### Tabs with Icons\n\n```rust\nuse gpui_component::{Icon, IconName};\n\nTabBar::new(\"icon-tabs\")\n    .child(Tab::default().icon(IconName::User).with_variant(TabVariant::Tab))\n    .child(Tab::default().icon(IconName::Settings).with_variant(TabVariant::Tab))\n    .child(Tab::default().icon(IconName::Mail).with_variant(TabVariant::Tab))\n```\n\n### Tabs with Prefix and Suffix\n\n```rust\nuse gpui_component::button::Button;\nuse gpui_component::{h_flex, IconName};\n\nTabBar::new(\"tabs-with-controls\")\n    .prefix(\n        h_flex()\n            .gap_1()\n            .child(Button::new(\"back\").ghost().xsmall().icon(IconName::ArrowLeft))\n            .child(Button::new(\"forward\").ghost().xsmall().icon(IconName::ArrowRight))\n    )\n    .suffix(\n        h_flex()\n            .gap_1()\n            .child(Button::new(\"inbox\").ghost().xsmall().icon(IconName::Inbox))\n            .child(Button::new(\"more\").ghost().xsmall().icon(IconName::Ellipsis))\n    )\n    .child(Tab::new().label(\"Account\"))\n    .child(Tab::new().label(\"Profile\"))\n    .child(Tab::new().label(\"Settings\"))\n```\n\n### Disabled Tabs\n\n```rust\nTabBar::new(\"tabs-with-disabled\")\n    .child(Tab::new().label(\"Account\"))\n    .child(Tab::new().label(\"Profile\").disabled(true))\n    .child(Tab::new().label(\"Settings\"))\n```\n\n### Dynamic Tabs\n\n```rust\nstruct TabsView {\n    active_tab: usize,\n    tabs: Vec<String>,\n}\n\nimpl Render for TabsView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        TabBar::new(\"dynamic-tabs\")\n            .selected_index(self.active_tab)\n            .on_click(cx.listener(|view, index, _, cx| {\n                view.active_tab = *index;\n                cx.notify();\n            }))\n            .children(\n                self.tabs\n                    .iter()\n                    .map(|tab_name| Tab::new().label(tab_name.clone()))\n            )\n    }\n}\n```\n\n### Tabs with Menu\n\nUse `menu` option to enable a dropdown menu for tab selection when there are many tabs,\nthis is default `false`.\n\nIf enable, the will have a dropdown button at the end of the tab bar to show all tabs in a menu.\n\n```rust\nTabBar::new(\"tabs-with-menu\")\n    .menu(true)\n    .selected_index(0)\n    .child(Tab::new().label(\"Account\"))\n    .child(Tab::new().label(\"Profile\"))\n    .child(Tab::new().label(\"Documents\"))\n    .child(Tab::new().label(\"Mail\"))\n    .child(Tab::new().label(\"Settings\"))\n```\n\n### Scrollable Tabs\n\n```rust\nuse gpui::ScrollHandle;\n\nstruct ScrollableTabsView {\n    scroll_handle: ScrollHandle,\n}\n\nimpl Render for ScrollableTabsView {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        TabBar::new(\"scrollable-tabs\")\n            .track_scroll(&self.scroll_handle)\n            .child(Tab::new().label(\"Very Long Tab Name 1\"))\n            .child(Tab::new().label(\"Very Long Tab Name 2\"))\n            .child(Tab::new().label(\"Very Long Tab Name 3\"))\n            .child(Tab::new().label(\"Very Long Tab Name 4\"))\n            .child(Tab::new().label(\"Very Long Tab Name 5\"))\n    }\n}\n```\n\n### Individual Tab Configuration\n\n```rust\nTabBar::new(\"custom-tabs\")\n    .child(\n        Tab::new().label(\"Custom Tab\")\n            .id(\"custom-id\")\n            .prefix(IconName::Star)\n            .suffix(IconName::X)\n            .on_click(|_, _, _| {\n                println!(\"Custom tab clicked\");\n            })\n    )\n```\n\n## API Reference\n\n### TabBar\n\n| Method                      | Description                                        |\n| --------------------------- | -------------------------------------------------- |\n| `new(id)`                   | Create a new tab bar with the given ID             |\n| `child(tab)`                | Add a tab to the bar                               |\n| `children(tabs)`            | Add multiple tabs to the bar                       |\n| `selected_index(index)`     | Set the active tab index                           |\n| `on_click(fn)`              | Callback when a tab is clicked, receives tab index |\n| `prefix(element)`           | Add element before the tabs                        |\n| `suffix(element)`           | Add element after the tabs                         |\n| `last_empty_space(element)` | Custom element for empty space at the end          |\n| `track_scroll(handle)`      | Enable scrolling with a scroll handle              |\n| `with_menu(bool)`           | Enable dropdown menu for tab selection             |\n\n### TabBar Variants\n\n| Method                  | Description                          |\n| ----------------------- | ------------------------------------ |\n| `with_variant(variant)` | Set the tab variant for all children |\n| `underline()`           | Use underline variant                |\n| `pill()`                | Use pill variant                     |\n| `outline()`             | Use outline variant                  |\n| `segmented()`           | Use segmented variant                |\n\n### Tab\n\n| Method                  | Description                                    |\n| ----------------------- | ---------------------------------------------- |\n| `new(label)`            | Create a new tab with a label                  |\n| `empty()`               | Create an empty tab                            |\n| `icon(icon)`            | Create a tab with only an icon                 |\n| `id(id)`                | Set custom ID for the tab                      |\n| `with_variant(variant)` | Set the tab variant                            |\n| `pill()`                | Use pill variant                               |\n| `outline()`             | Use outline variant                            |\n| `segmented()`           | Use segmented variant                          |\n| `underline()`           | Use underline variant                          |\n| `prefix(element)`       | Add element before tab content                 |\n| `suffix(element)`       | Add element after tab content                  |\n| `disabled(bool)`        | Set disabled state                             |\n| `selected(bool)`        | Set selected state (usually handled by TabBar) |\n| `on_click(fn)`          | Custom click handler for individual tab        |\n\n### TabVariant\n\n```rust\npub enum TabVariant {\n    Tab,      // Default bordered tabs\n    Outline,  // Rounded outline tabs\n    Pill,     // Rounded pill-shaped tabs\n    Segmented, // Segmented control style\n    Underline, // Underline indicator tabs\n}\n```\n\n### Styling\n\nBoth `TabBar` and `Tab` implement `Sizable` trait:\n\n- `xsmall()` - Extra small size\n- `small()` - Small size\n- `medium()` - Medium size (default)\n- `large()` - Large size\n\n## Advanced Examples\n\n### Custom Tab Content\n\n```rust\nTab::empty()\n    .child(\n        h_flex()\n            .items_center()\n            .gap_2()\n            .child(IconName::Folder)\n            .child(\"Documents\")\n            .child(\n                div()\n                    .px_1()\n                    .py_0p5()\n                    .text_xs()\n                    .bg(cx.theme().accent)\n                    .text_color(cx.theme().accent_foreground)\n                    .rounded(cx.theme().radius.half())\n                    .child(\"12\")\n            )\n    )\n```\n\n### Tabs with State Management\n\n```rust\nstruct TabsWithContent {\n    active_tab: usize,\n    tab_contents: Vec<String>,\n}\n\nimpl TabsWithContent {\n    fn render_tab_content(&self, cx: &mut Context<Self>) -> impl IntoElement {\n        match self.active_tab {\n            0 => div().child(\"Account content\"),\n            1 => div().child(\"Profile content\"),\n            2 => div().child(\"Settings content\"),\n            _ => div().child(\"Unknown content\"),\n        }\n    }\n}\n\nimpl Render for TabsWithContent {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .child(\n                TabBar::new(\"content-tabs\")\n                    .selected_index(self.active_tab)\n                    .on_click(cx.listener(|view, index, _, cx| {\n                        view.active_tab = *index;\n                        cx.notify();\n                    }))\n                    .child(Tab::new().label(\"Account\"))\n                    .child(Tab::new().label(\"Profile\"))\n                    .child(Tab::new().label(\"Settings\"))\n            )\n            .child(\n                div()\n                    .flex_1()\n                    .p_4()\n                    .child(self.render_tab_content(cx))\n            )\n    }\n}\n```\n\n### Tabs with Close Buttons\n\nWhile the basic Tab component doesn't include closeable functionality, you can create closeable tabs using suffix elements:\n\n```rust\nstruct CloseableTabsView {\n    tabs: Vec<String>,\n    active_tab: usize,\n}\n\nimpl CloseableTabsView {\n    fn close_tab(&mut self, index: usize, cx: &mut Context<Self>) {\n        if self.tabs.len() > 1 {\n            self.tabs.remove(index);\n            if self.active_tab >= index && self.active_tab > 0 {\n                self.active_tab -= 1;\n            }\n            cx.notify();\n        }\n    }\n}\n\nimpl Render for CloseableTabsView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        TabBar::new(\"closeable-tabs\")\n            .selected_index(self.active_tab)\n            .on_click(cx.listener(|view, index, _, cx| {\n                view.active_tab = *index;\n                cx.notify();\n            }))\n            .children(\n                self.tabs\n                    .iter()\n                    .enumerate()\n                    .map(|(index, tab_name)| {\n                        Tab::new().label(tab_name.clone())\n                            .suffix(\n                                Button::new(format!(\"close-{}\", index))\n                                    .icon(IconName::X)\n                                    .ghost()\n                                    .xsmall()\n                                    .on_click(cx.listener(move |view, _, _, cx| {\n                                        view.close_tab(index, cx);\n                                    }))\n                            )\n                    })\n            )\n    }\n}\n```\n\n## Notes\n\n- The `TabBar` manages the selection state of all child tabs\n- Individual tab `on_click` handlers are ignored when `TabBar.on_click` is set\n- Tabs automatically inherit the variant and size from their parent `TabBar`\n- The `with_menu` option adds a dropdown for tab selection when there are many tabs\n- Scrolling is automatically enabled when tabs overflow the container width\n- The dock system provides advanced closeable tab functionality for complex layouts\n"
  },
  {
    "path": "docs/docs/components/tag.md",
    "content": "---\ntitle: Tag\ndescription: A short item that can be used to categorize or label content.\n---\n\n# Tag\n\nA versatile tag component for categorizing and labeling content. Tags are compact visual indicators that help organize information and display metadata like categories, status, or properties.\n\n## Import\n\n```rust\nuse gpui_component::tag::Tag;\n```\n\n## Usage\n\n### Basic Tags\n\n```rust\n// Primary tag (default filled style)\nTag::primary().child(\"Primary\")\n\n// Secondary tag\nTag::secondary().child(\"Secondary\")\n\n// Status tags\nTag::danger().child(\"Danger\")\nTag::success().child(\"Success\")\nTag::warning().child(\"Warning\")\nTag::info().child(\"Info\")\n```\n\n### Tag Variants\n\n```rust\n// Semantic variants\nTag::primary().child(\"Featured\")\nTag::secondary().child(\"Category\")\nTag::danger().child(\"Critical\")\nTag::success().child(\"Completed\")\nTag::warning().child(\"Pending\")\nTag::info().child(\"Information\")\n```\n\n### Outline Tags\n\n```rust\n// Outline style variants\nTag::primary().outline().child(\"Primary Outline\")\nTag::secondary().outline().child(\"Secondary Outline\")\nTag::danger().outline().child(\"Error Outline\")\nTag::success().outline().child(\"Success Outline\")\nTag::warning().outline().child(\"Warning Outline\")\nTag::info().outline().child(\"Info Outline\")\n```\n\n### Tag Sizes\n\n```rust\n// Small size\nTag::primary().small().child(\"Small Tag\")\n\n// Medium size (default)\nTag::primary().child(\"Medium Tag\")\n```\n\n### Custom Colors\n\n```rust\nuse gpui_component::ColorName;\n\n// Using predefined color names\nTag::color(ColorName::Blue).child(\"Blue Tag\")\nTag::color(ColorName::Green).child(\"Green Tag\")\nTag::color(ColorName::Purple).child(\"Purple Tag\")\nTag::color(ColorName::Pink).child(\"Pink Tag\")\nTag::color(ColorName::Indigo).child(\"Indigo Tag\")\nTag::color(ColorName::Yellow).child(\"Yellow Tag\")\nTag::color(ColorName::Red).child(\"Red Tag\")\n```\n\n### Custom HSLA Colors\n\n```rust\nuse gpui::{hsla, Hsla};\n\n// Custom colors with HSLA values\nlet color = hsla(220.0 / 360.0, 0.8, 0.5, 1.0);\nlet foreground = hsla(0.0, 0.0, 1.0, 1.0);\nlet border = hsla(220.0 / 360.0, 0.8, 0.4, 1.0);\n\nTag::custom(color, foreground, border).child(\"Custom Color\")\n```\n\n### Rounded Corners\n\n```rust\nuse gpui::px;\n\n// Fully rounded tags\nTag::primary().rounded_full().child(\"Rounded Full\")\n\n// Custom border radius\nTag::primary().rounded(px(4.0)).child(\"Custom Radius\")\n\n// Square corners\nTag::primary().rounded(px(0.0)).child(\"Square Tag\")\n```\n\n### Combined Styles\n\n```rust\n// Small tags with full rounding\nTag::primary().small().rounded_full().child(\"Small Pill\")\nTag::success().small().rounded_full().child(\"Success Pill\")\n\n// Outline tags with custom rounding\nTag::warning().outline().rounded(px(2.0)).child(\"Custom Outline\")\n\n// Color tags with outline style\nTag::color(ColorName::Purple).outline().child(\"Purple Outline\")\n```\n\n## Tag Categories and Use Cases\n\n### Status Tags\n\n```rust\n// Task or item status\nTag::success().child(\"Completed\")\nTag::warning().child(\"In Progress\")\nTag::danger().child(\"Failed\")\nTag::info().child(\"Pending Review\")\n```\n\n### Category Labels\n\n```rust\n// Content categorization\nTag::secondary().child(\"Technology\")\nTag::color(ColorName::Blue).child(\"Design\")\nTag::color(ColorName::Green).child(\"Development\")\nTag::color(ColorName::Purple).child(\"Marketing\")\n```\n\n### Priority Indicators\n\n```rust\n// Priority levels\nTag::danger().child(\"High Priority\")\nTag::warning().child(\"Medium Priority\")\nTag::secondary().child(\"Low Priority\")\n```\n\n### Feature Tags\n\n```rust\n// Feature flags or attributes\nTag::primary().small().child(\"New\")\nTag::success().small().child(\"Popular\")\nTag::info().small().child(\"Beta\")\nTag::warning().small().child(\"Limited\")\n```\n\n## API Reference\n\n### Tag Creation Methods\n\n| Method                      | Description                                |\n| --------------------------- | ------------------------------------------ |\n| `primary()`                 | Create a primary tag (blue theme)          |\n| `secondary()`               | Create a secondary tag (gray theme)        |\n| `danger()`                  | Create a danger tag (red theme)            |\n| `success()`                 | Create a success tag (green theme)         |\n| `warning()`                 | Create a warning tag (yellow/orange theme) |\n| `info()`                    | Create an info tag (blue theme)            |\n| `color(ColorName)`          | Create a tag with predefined color         |\n| `custom(color, fg, border)` | Create a tag with custom HSLA colors       |\n\n### Style Methods\n\n| Method            | Description                                  |\n| ----------------- | -------------------------------------------- |\n| `outline()`       | Apply outline style (transparent background) |\n| `rounded(radius)` | Set custom border radius                     |\n| `rounded_full()`  | Apply full rounding (pill shape)             |\n\n### Size Methods (from Sizable trait)\n\n| Method            | Description                      |\n| ----------------- | -------------------------------- |\n| `small()`         | Small tag size (reduced padding) |\n| `with_size(size)` | Set custom size                  |\n\n### Content Methods (from ParentElement trait)\n\n| Method           | Description                  |\n| ---------------- | ---------------------------- |\n| `child(element)` | Add child content to the tag |\n\n## Examples\n\n### Tag Collections\n\n```rust\nuse gpui_component::{h_flex, v_flex};\n\n// Horizontal tag group\nh_flex()\n    .gap_2()\n    .child(Tag::primary().child(\"React\"))\n    .child(Tag::success().child(\"TypeScript\"))\n    .child(Tag::info().child(\"Next.js\"))\n    .child(Tag::warning().child(\"Beta\"))\n\n// Vertical tag stack\nv_flex()\n    .gap_1()\n    .child(Tag::danger().small().child(\"Critical\"))\n    .child(Tag::warning().small().child(\"Important\"))\n    .child(Tag::secondary().small().child(\"Normal\"))\n```\n\n### Status Dashboard Tags\n\n```rust\n// System status indicators\nh_flex()\n    .gap_3()\n    .child(\n        v_flex()\n            .child(\"API Status:\")\n            .child(Tag::success().child(\"Operational\"))\n    )\n    .child(\n        v_flex()\n            .child(\"Database:\")\n            .child(Tag::warning().child(\"Maintenance\"))\n    )\n    .child(\n        v_flex()\n            .child(\"Cache:\")\n            .child(Tag::danger().child(\"Down\"))\n    )\n```\n\n### Interactive Tag Lists\n\n```rust\n// Note: Event handling would require additional state management\n// Tags themselves are display components\n\n// Filter tags (would need click handlers)\nh_flex()\n    .gap_2()\n    .child(Tag::primary().small().child(\"All\"))\n    .child(Tag::secondary().outline().small().child(\"Active\"))\n    .child(Tag::secondary().outline().small().child(\"Completed\"))\n    .child(Tag::secondary().outline().small().child(\"Archived\"))\n```\n\n### Color-Coded Categories\n\n```rust\nuse gpui_component::ColorName;\n\n// Content type tags\nh_flex()\n    .gap_2()\n    .flex_wrap()\n    .child(Tag::color(ColorName::Red).child(\"Bug\"))\n    .child(Tag::color(ColorName::Blue).child(\"Feature\"))\n    .child(Tag::color(ColorName::Green).child(\"Enhancement\"))\n    .child(Tag::color(ColorName::Purple).child(\"Documentation\"))\n    .child(Tag::color(ColorName::Yellow).child(\"Question\"))\n    .child(Tag::color(ColorName::Pink).child(\"Discussion\"))\n```\n\n### Pill-Style Tags\n\n```rust\n// Skill tags with pill styling\nh_flex()\n    .gap_2()\n    .flex_wrap()\n    .child(Tag::color(ColorName::Blue).rounded_full().small().child(\"Rust\"))\n    .child(Tag::color(ColorName::Green).rounded_full().small().child(\"JavaScript\"))\n    .child(Tag::color(ColorName::Purple).rounded_full().small().child(\"Python\"))\n    .child(Tag::color(ColorName::Red).rounded_full().small().child(\"Go\"))\n```\n\n## Behavior Notes\n\n- Tags automatically adjust their appearance based on the current theme\n- Outline tags maintain border visibility across different backgrounds\n- Small tags use reduced padding and border radius for compact layouts\n- Custom colors support both light and dark theme adaptations\n- Tags are display components and don't include built-in interaction handlers\n- Multiple tags can be combined in flex layouts for tag clouds or lists\n- Border radius automatically scales based on tag size unless explicitly overridden\n\n## Design Guidelines\n\n### When to Use Tags\n\n- **Categorization**: Group content by type, topic, or theme\n- **Status Indication**: Show state, progress, or health status\n- **Metadata Display**: Present attributes, properties, or classifications\n- **Filtering**: Visual indicators for active filters or selections\n- **Feature Flags**: Highlight new, beta, or special features\n\n### Color Usage\n\n- **Semantic Colors**: Use danger (red) for errors, success (green) for completion, warning (yellow) for caution, info (blue) for information\n- **Category Colors**: Use the ColorName variants for content categorization where color coding helps with recognition\n- **Custom Colors**: Reserve for brand colors or specific design system requirements\n\n### Size Guidelines\n\n- **Small Tags**: Use for compact layouts, metadata, or when space is limited\n- **Medium Tags**: Default size for most use cases, provides good readability and click targets\n- **Rounding**: Use `rounded_full()` for pill-style tags, custom `rounded()` for specific design requirements\n"
  },
  {
    "path": "docs/docs/components/title-bar.md",
    "content": "---\ntitle: TitleBar\ndescription: A custom window title bar component with window controls and custom content support.\n---\n\n# TitleBar\n\nTitleBar provides a customizable window title bar that can replace the default OS title bar. It includes platform-specific window controls (minimize, maximize, close) and supports custom content and styling. The component automatically adapts to different operating systems (macOS, Windows, Linux) with appropriate behaviors and visual styles.\n\n## Import\n\n```rust\nuse gpui_component::TitleBar;\n```\n\n## Usage\n\n### Basic Title Bar\n\n```rust\nTitleBar::new()\n    .child(div().child(\"My Application\"))\n```\n\n### Title Bar with Custom Content\n\n```rust\nTitleBar::new()\n    .child(\n        div()\n            .flex()\n            .items_center()\n            .gap_3()\n            .child(\"App Name\")\n            .child(Badge::new().count(5))\n    )\n    .child(\n        div()\n            .flex()\n            .items_center()\n            .gap_2()\n            .child(Button::new(\"settings\").icon(IconName::Settings))\n            .child(Button::new(\"profile\").icon(IconName::User))\n    )\n```\n\n### Title Bar with Menu Bar\n\n```rust\nTitleBar::new()\n    .child(\n        div()\n            .flex()\n            .items_center()\n            .child(AppMenuBar::new(window, cx))\n    )\n    .child(\n        div()\n            .flex()\n            .items_center()\n            .justify_end()\n            .gap_2()\n            .child(Button::new(\"github\").icon(IconName::GitHub))\n            .child(Button::new(\"notifications\").icon(IconName::Bell))\n    )\n```\n\n### Title Bar with Window Controls (Linux only)\n\n```rust\nTitleBar::new()\n    .on_close_window(|_, window, cx| {\n        // Custom close behavior\n        window.push_notification(\"Saving before close...\", cx);\n        // Perform cleanup\n        window.remove_window();\n    })\n    .child(div().child(\"Custom Close Behavior\"))\n```\n\n### Styled Title Bar\n\n```rust\nTitleBar::new()\n    .bg(cx.theme().primary)\n    .border_color(cx.theme().primary_border)\n    .child(\n        div()\n            .text_color(cx.theme().primary_foreground)\n            .child(\"Styled Title Bar\")\n    )\n```\n\n### Title Bar Options for Window\n\n```rust\nuse gpui::{WindowOptions, TitlebarOptions};\n\nWindowOptions {\n    titlebar: Some(TitleBar::title_bar_options()),\n    ..Default::default()\n}\n```\n\n## Platform Differences\n\n### macOS\n\n- Uses native traffic light buttons (minimize, maximize, close)\n- Traffic light position is automatically set to `(9px, 9px)`\n- Double-click behavior calls `window.titlebar_double_click()`\n- Left padding accounts for traffic light buttons (80px)\n- Appears transparent by default\n\n### Windows\n\n- Custom window control buttons with system integration\n- Uses `WindowControlArea` for proper window management\n- Control buttons have hover and active states\n- Fixed button width of 34px each\n- Left padding is 12px\n\n### Linux\n\n- Custom window control buttons with manual event handling\n- Supports custom close window callback via `on_close_window()`\n- Double-click to maximize/restore window\n- Right-click shows window context menu\n- Window dragging supported in title bar area\n\n## API Reference\n\n### TitleBar\n\n| Method                | Description                              |\n| --------------------- | ---------------------------------------- |\n| `new()`               | Create a new title bar                   |\n| `child(element)`      | Add child element to the title bar       |\n| `on_close_window(fn)` | Custom close window handler (Linux only) |\n| `title_bar_options()` | Get default titlebar options for window  |\n\n### Window Configuration\n\n| Property                 | Description                                         |\n| ------------------------ | --------------------------------------------------- |\n| `appears_transparent`    | Make title bar transparent (default: true)          |\n| `traffic_light_position` | Position of macOS traffic lights                    |\n| `title`                  | Window title (optional when using custom title bar) |\n\n### Title Bar Element (Internal)\n\nThe `TitleBarElement` provides window dragging functionality on Linux platforms.\n\n### Constants\n\n| Constant                 | Value                           | Description               |\n| ------------------------ | ------------------------------- | ------------------------- |\n| `TITLE_BAR_HEIGHT`       | `34px`                          | Standard title bar height |\n| `TITLE_BAR_LEFT_PADDING` | `80px` (macOS), `12px` (others) | Left padding for content  |\n\n## Examples\n\n### Application Title Bar\n\n```rust\nuse gpui_component::{TitleBar, button::Button, menu::AppMenuBar};\n\nstruct AppTitleBar {\n    app_menu_bar: Entity<AppMenuBar>,\n}\n\nimpl Render for AppTitleBar {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        TitleBar::new()\n            .child(\n                div()\n                    .flex()\n                    .items_center()\n                    .child(self.app_menu_bar.clone())\n            )\n            .child(\n                div()\n                    .flex()\n                    .items_center()\n                    .justify_end()\n                    .gap_2()\n                    .child(\n                        Button::new(\"settings\")\n                            .ghost()\n                            .icon(IconName::Settings)\n                    )\n                    .child(\n                        Button::new(\"help\")\n                            .ghost()\n                            .icon(IconName::HelpCircle)\n                    )\n            )\n    }\n}\n```\n\n### Title Bar with Breadcrumbs\n\n```rust\nTitleBar::new()\n    .child(\n        div()\n            .flex()\n            .items_center()\n            .gap_2()\n            .child(\"Home\")\n            .child(IconName::ChevronRight)\n            .child(\"Documents\")\n            .child(IconName::ChevronRight)\n            .child(\"Project\")\n    )\n    .child(\n        div()\n            .flex()\n            .items_center()\n            .gap_1()\n            .child(Button::new(\"search\").icon(IconName::Search).ghost())\n            .child(Button::new(\"more\").icon(IconName::MoreHorizontal).ghost())\n    )\n```\n\n### Custom Themed Title Bar\n\n```rust\nTitleBar::new()\n    .h(px(40.)) // Custom height\n    .bg(cx.theme().accent)\n    .border_b_2()\n    .border_color(cx.theme().accent_border)\n    .child(\n        div()\n            .flex()\n            .items_center()\n            .text_color(cx.theme().accent_foreground)\n            .font_weight_semibold()\n            .child(\"Custom Theme App\")\n    )\n```\n\n### Title Bar with Status\n\n```rust\nTitleBar::new()\n    .child(\n        div()\n            .flex()\n            .items_center()\n            .gap_3()\n            .child(\"My Editor\")\n            .child(\n                div()\n                    .text_xs()\n                    .text_color(cx.theme().muted_foreground)\n                    .child(\"● Unsaved changes\")\n            )\n    )\n    .child(\n        div()\n            .flex()\n            .items_center()\n            .gap_2()\n            .child(\n                div()\n                    .text_xs()\n                    .text_color(cx.theme().muted_foreground)\n                    .child(\"Line 42, Col 12\")\n            )\n            .child(\n                Button::new(\"sync\")\n                    .small()\n                    .ghost()\n                    .icon(IconName::RotateCcw)\n                    .tooltip(\"Sync changes\")\n            )\n    )\n```\n\n### Minimal Title Bar\n\n```rust\nTitleBar::new()\n    .child(\n        div()\n            .text_center()\n            .flex_1()\n            .child(\"Document.txt\")\n    )\n```\n\n### Title Bar with Search\n\n```rust\nTitleBar::new()\n    .child(\n        div()\n            .flex()\n            .items_center()\n            .gap_3()\n            .child(\"File Explorer\")\n            .child(\n                Input::new(\"search\")\n                    .placeholder(\"Search files...\")\n                    .w(px(200.))\n                    .small()\n            )\n    )\n```\n\n## Notes\n\n- The title bar automatically handles platform-specific styling and behavior\n- Window controls are only rendered on Windows and Linux platforms\n- The component integrates with GPUI's window management system\n- Custom styling should consider platform conventions\n- Window dragging is handled automatically in appropriate areas\n"
  },
  {
    "path": "docs/docs/components/toggle.md",
    "content": "---\ntitle: Toggle\ndescription: A button-style toggle component for binary on/off or selected states.\n---\n\n# Toggle\n\nA button-style toggle component that represents on/off or selected states. Unlike a traditional switch, toggles appear as buttons that can be pressed in or out. They're perfect for toolbar buttons, filter options, or any binary choice that benefits from a button-like appearance.\n\n## Import\n\n```rust\nuse gpui_component::button::{Toggle, ToggleGroup};\n```\n\n## Usage\n\n### Basic Toggle\n\n```rust\nToggle::new(\"toggle1\").\n    .label(\"Toggle me\")\n    .checked(false)\n    .on_click(|checked, _, _| {\n        println!(\"Toggle is now: {}\", checked);\n    })\n```\n\nHere, we can use `on_click` to handle toggle state changes. The callback receives the **new checked state** as a `bool`.\n\n### Icon Toggle\n\n```rust\nuse gpui_component::IconName;\n\nToggle::new(\"toggle2\")\n    .icon(IconName::Eye)\n    .checked(true)\n    .on_click(|checked, _, _| {\n        println!(\"Visibility: {}\", if *checked { \"shown\" } else { \"hidden\" });\n    })\n```\n\n### Controlled Toggle\n\n```rust\nstruct MyView {\n    is_active: bool,\n}\n\nimpl Render for MyView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        Toggle::new(\"active\")\n            .label(\"Active\")\n            .checked(self.is_active)\n            .on_click(cx.listener(|view, checked, _, cx| {\n                view.is_active = *checked;\n                cx.notify();\n            }))\n    }\n}\n```\n\n### Toggle Variants\n\n```rust\n// Ghost toggle (default)\nToggle::new(\"ghost-toggle\")\n    .ghost()\n    .label(\"Ghost\")\n\n// Outline toggle\nToggle::new(\"outline-toggle\")\n    .outline()\n    .label(\"Outline\")\n```\n\n### Different Sizes\n\n```rust\n// Extra small\nToggle::new(\"xs-toggle\")\n    .icon(IconName::Star)\n    .xsmall()\n\n// Small\nToggle::new(\"small-toggle\")\n    .label(\"Small\")\n    .small()\n\n// Medium (default)\nToggle::new(\"medium-toggle\")\n    .label(\"Medium\")\n\n\n// Large\nToggle::new(\"large-toggle\")\n    .label(\"Large\")\n    .large()\n```\n\n### Disabled State\n\n```rust\n// Disabled unchecked\nToggle::new(\"disabled-toggle\")\n    .label(\"Disabled\")\n    .disabled(true)\n    .checked(false)\n\n// Disabled checked\nToggle::new(\"disabled-checked-toggle\")\n    .label(\"Selected (Disabled)\")\n    .disabled(true)\n    .checked(true)\n```\n\n## Toggle vs Switch\n\n| Feature                | Toggle                                      | Switch                                    |\n| ---------------------- | ------------------------------------------- | ----------------------------------------- |\n| **Appearance**         | Button-like, can be pressed in/out          | Traditional switch with sliding indicator |\n| **Use Cases**          | Toolbar buttons, filters, binary options    | Settings, preferences, on/off states      |\n| **Visual Style**       | Rectangular button shape                    | Rounded switch track with thumb           |\n| **State Indication**   | Background color change, pressed appearance | Position of sliding thumb                 |\n| **Multiple Selection** | Supports groups with multiple selection     | Individual switches only                  |\n\n**Use Toggle when you want:**\n\n- Button-like appearance for binary states\n- Grouping multiple related options\n- Toolbar or filter interfaces\n- Options that feel like \"selections\" rather than \"settings\"\n\n**Use Switch when you want:**\n\n- Traditional on/off control appearance\n- Settings or preferences interface\n- Clear visual indication of state with sliding animation\n- Individual boolean controls\n\n## Integration with ToggleGroup\n\nToggle buttons can be grouped together using `ToggleGroup` for related options:\n\n### Basic Toggle Group\n\n```rust\nToggleGroup::new(\"filter-group\")\n    .child(Toggle::new(0).icon(IconName::Bell))\n    .child(Toggle::new(1).icon(IconName::Bot))\n    .child(Toggle::new(2).icon(IconName::Inbox))\n    .child(Toggle::new(3).label(\"Other\"))\n    .on_click(|checkeds, _, _| {\n        println!(\"Selected toggles: {:?}\", checkeds);\n    })\n```\n\nThe `on_click` callback receives a `Vec<bool>` representing the **new checked state** of each toggle in the group.\n\n### Toggle Group with Controlled State\n\n```rust\nstruct FilterView {\n    notifications: bool,\n    bots: bool,\n    inbox: bool,\n    other: bool,\n}\n\nimpl Render for FilterView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        ToggleGroup::new(\"filters\")\n            .child(Toggle::new(0).icon(IconName::Bell).checked(self.notifications))\n            .child(Toggle::new(1).icon(IconName::Bot).checked(self.bots))\n            .child(Toggle::new(2).icon(IconName::Inbox).checked(self.inbox))\n            .child(Toggle::new(3).label(\"Other\").checked(self.other))\n            .on_click(cx.listener(|view, checkeds, _, cx| {\n                view.notifications = checkeds[0];\n                view.bots = checkeds[1];\n                view.inbox = checkeds[2];\n                view.other = checkeds[3];\n                cx.notify();\n            }))\n    }\n}\n```\n\n### Toggle Group Variants and Sizes\n\n```rust\n// Outline variant, small size\nToggleGroup::new(\"compact-filters\")\n    .outline()\n    .small()\n    .child(Toggle::new(0).icon(IconName::Filter))\n    .child(Toggle::new(1).icon(IconName::Sort))\n    .child(Toggle::new(2).icon(IconName::Search))\n\n// Ghost variant (default), extra small\nToggleGroup::new(\"mini-toolbar\")\n    .xsmall()\n    .child(Toggle::new(0).icon(IconName::Bold))\n    .child(Toggle::new(1).icon(IconName::Italic))\n    .child(Toggle::new(2).icon(IconName::Underline))\n```\n\n## Event Handling\n\n### Individual Toggle Events\n\n```rust\nToggle::new(\"subscribe-toggle\")\n    .label(\"Subscribe\")\n    .on_click(|checked, window, cx| {\n        if *checked {\n            // Handle subscription logic\n            println!(\"Subscribed!\");\n        } else {\n            // Handle unsubscription logic\n            println!(\"Unsubscribed!\");\n        }\n    })\n```\n\n## Examples\n\n### Toolbar with Toggle Buttons\n\n```rust\nstruct EditorToolbar {\n    bold: bool,\n    italic: bool,\n    underline: bool,\n    strikethrough: bool,\n}\n\nh_flex()\n    .gap_1()\n    .p_2()\n    .bg(cx.theme().background)\n    .border_1()\n    .border_color(cx.theme().border)\n    .child(\n        ToggleGroup::new(\"formatting\")\n            .small()\n            .child(Toggle::new(0).icon(IconName::Bold).checked(self.bold))\n            .child(Toggle::new(1).icon(IconName::Italic).checked(self.italic))\n            .child(Toggle::new(2).icon(IconName::Underline).checked(self.underline))\n            .child(Toggle::new(3).icon(IconName::Strikethrough).checked(self.strikethrough))\n            .on_click(cx.listener(|view, states, _, cx| {\n                view.bold = states[0];\n                view.italic = states[1];\n                view.underline = states[2];\n                view.strikethrough = states[3];\n                cx.notify();\n            }))\n    )\n```\n\n### Filter Interface\n\n```rust\nstruct FilterPanel {\n    show_completed: bool,\n    show_pending: bool,\n    show_cancelled: bool,\n    show_urgent: bool,\n}\n\nv_flex()\n    .gap_3()\n    .p_4()\n    .child(Label::new(\"Filter by status\"))\n    .child(\n        ToggleGroup::new(\"status-filters\")\n            .outline()\n            .child(Toggle::new(0).label(\"Completed\").checked(self.show_completed))\n            .child(Toggle::new(1).label(\"Pending\").checked(self.show_pending))\n            .child(Toggle::new(2).label(\"Cancelled\").checked(self.show_cancelled))\n            .on_click(cx.listener(|view, states, _, cx| {\n                view.show_completed = states[0];\n                view.show_pending = states[1];\n                view.show_cancelled = states[2];\n                cx.notify();\n            }))\n    )\n    .child(\n        Toggle::new(\"urgent-filter\")\n            .label(\"Show urgent only\")\n            .checked(self.show_urgent)\n            .on_click(cx.listener(|view, checked, _, cx| {\n                view.show_urgent = *checked;\n                cx.notify();\n            }))\n    )\n```\n\n### Settings with Individual Toggles\n\n```rust\nstruct NotificationSettings {\n    email_notifications: bool,\n    push_notifications: bool,\n    marketing_emails: bool,\n}\n\nv_flex()\n    .gap_4()\n    .child(\n        h_flex()\n            .items_center()\n            .justify_between()\n            .child(\n                v_flex()\n                    .child(Label::new(\"Email notifications\"))\n                    .child(\n                        Label::new(\"Receive notifications via email\")\n                            .text_color(cx.theme().muted_foreground)\n                            .text_sm()\n                    )\n            )\n            .child(\n                Toggle::new(\"email-notifications\")\n                    .icon(IconName::Mail)\n                    .checked(self.email_notifications)\n                    .on_click(cx.listener(|view, checked, _, cx| {\n                        view.email_notifications = *checked;\n                        cx.notify();\n                    }))\n            )\n    )\n    .child(\n        h_flex()\n            .items_center()\n            .justify_between()\n            .child(Label::new(\"Push notifications\"))\n            .child(\n                Toggle::new(\"push-notifications\")\n                    .icon(IconName::Bell)\n                    .checked(self.push_notifications)\n                    .on_click(cx.listener(|view, checked, _, cx| {\n                        view.push_notifications = *checked;\n                        cx.notify();\n                    }))\n            )\n    )\n```\n\n### Multi-select Options\n\n```rust\nstruct SelectionView {\n    selected_categories: Vec<bool>,\n}\n\nimpl SelectionView {\n    fn categories() -> Vec<&'static str> {\n        vec![\"Technology\", \"Design\", \"Business\", \"Science\", \"Art\"]\n    }\n}\n\nv_flex()\n    .gap_3()\n    .child(Label::new(\"Select categories of interest\"))\n    .child(\n        ToggleGroup::new(\"categories\")\n            .children(\n                Self::categories()\n                    .into_iter()\n                    .enumerate()\n                    .map(|(i, category)| {\n                        Toggle::new(i)\n                            .label(category)\n                            .checked(self.selected_categories.get(i).copied().unwrap_or(false))\n                    })\n            )\n            .on_click(cx.listener(|view, states, _, cx| {\n                view.selected_categories = states.clone();\n                cx.notify();\n            }))\n    )\n```\n\n## Best Practices\n\n1. **Use meaningful labels**: Choose clear, descriptive text for toggle labels\n2. **Group related options**: Use ToggleGroup for logically related binary choices\n3. **Provide visual feedback**: The checked state should be clearly distinguishable\n4. **Consider context**: Use toggles for options that feel like \"selections\" rather than \"settings\"\n5. **Maintain state consistency**: Ensure toggle state reflects the actual application state\n6. **Accessible labels**: Provide tooltips or ARIA labels for icon-only toggles\n"
  },
  {
    "path": "docs/docs/components/tooltip.md",
    "content": "---\ntitle: Tooltip\ndescription: Display helpful information on hover or focus, with support for keyboard shortcuts and custom content.\n---\n\n# Tooltip\n\nA versatile tooltip component that displays helpful information when hovering over or focusing on elements. Supports text content, custom elements, keyboard shortcuts, different trigger methods, and positioning options.\n\n## Import\n\n```rust\nuse gpui_component::tooltip::Tooltip;\n```\n\n## Usage\n\n### Basic Tooltip with Text\n\n```rust\n// Simple text tooltip\ndiv()\n    .child(\"Hover me\")\n    .id(\"basic-tooltip\")\n    .tooltip(|window, cx| {\n        Tooltip::new(\"This is a helpful tooltip\").build(window, cx)\n    })\n```\n\n### Button with Tooltip\n\n```rust\nButton::new(\"save-btn\")\n    .label(\"Save\")\n    .tooltip(\"Save the current document\")\n```\n\n### Tooltip with Action/Keybinding\n\n```rust\nactions!(my_actions, [SaveDocument]);\n\nButton::new(\"save-btn\")\n    .label(\"Save\")\n    .tooltip_with_action(\n        \"Save the current document\",\n        &SaveDocument,\n        Some(\"MyContext\")\n    )\n```\n\n### Custom Element Tooltip\n\n```rust\ndiv()\n    .child(\"Hover for rich content\")\n    .id(\"rich-tooltip\")\n    .tooltip(|window, cx| {\n        Tooltip::element(|_, cx| {\n            h_flex()\n                .gap_x_1()\n                .child(IconName::Info)\n                .child(\n                    div()\n                        .child(\"Muted Text\")\n                        .text_color(cx.theme().muted_foreground)\n                )\n                .child(\n                    div()\n                        .child(\"Danger Text\")\n                        .text_color(cx.theme().danger)\n                )\n                .child(IconName::ArrowUp)\n        })\n        .build(window, cx)\n    })\n```\n\n### Tooltip with Manual Keybinding\n\n```rust\ndiv()\n    .child(\"Custom keybinding\")\n    .id(\"custom-kb\")\n    .tooltip(|window, cx| {\n        Tooltip::new(\"Delete item\")\n            .key_binding(Some(Kbd::new(\"Delete\")))\n            .build(window, cx)\n    })\n```\n\n## Advanced Usage\n\n### Components with Built-in Tooltip Support\n\nMany components have built-in tooltip methods:\n\n```rust\n// Button\nButton::new(\"btn\")\n    .label(\"Click me\")\n    .tooltip(\"This button performs an action\")\n\n// Switch\nSwitch::new(\"toggle\")\n    .label(\"Enable notifications\")\n    .tooltip(\"Toggle push notifications on/off\")\n\n// Checkbox\nCheckbox::new(\"check\")\n    .label(\"Remember me\")\n    .tooltip(\"Keep me logged in for 30 days\")\n\n// Radio\nRadio::new(\"option\")\n    .label(\"Option 1\")\n    .tooltip(\"Select this option to enable feature X\")\n```\n\n### Complex Tooltip Content\n\n```rust\ndiv()\n    .child(\"Hover for details\")\n    .id(\"complex-tooltip\")\n    .tooltip(|window, cx| {\n        Tooltip::element(|_, cx| {\n            v_flex()\n                .gap_2()\n                .child(\n                    h_flex()\n                        .gap_1()\n                        .child(IconName::User)\n                        .child(\"User Information\")\n                        .text_sm()\n                        .font_semibold()\n                )\n                .child(\n                    div()\n                        .child(\"Last login: 2 hours ago\")\n                        .text_xs()\n                        .text_color(cx.theme().muted_foreground)\n                )\n                .child(\n                    div()\n                        .child(\"Status: Active\")\n                        .text_xs()\n                        .text_color(cx.theme().success)\n                )\n        })\n        .build(window, cx)\n    })\n```\n\n### Tooltip in Form Elements\n\n```rust\nv_flex()\n    .gap_4()\n    .child(\n        Input::new(\"email\")\n            .placeholder(\"Enter your email\")\n            .tooltip(\"We'll never share your email address\")\n    )\n    .child(\n        Input::new(\"password\")\n            .input_type(InputType::Password)\n            .placeholder(\"Password\")\n            .tooltip(\"Must be at least 8 characters with special characters\")\n    )\n```\n\n## API Reference\n\n### Tooltip\n\n| Method                    | Description                                  |\n| ------------------------- | -------------------------------------------- |\n| `new(text)`               | Create a tooltip with text content           |\n| `element(builder)`        | Create a tooltip with custom element content |\n| `action(action, context)` | Set action to display keybinding information |\n| `key_binding(kbd)`        | Set manual keybinding information            |\n| `build(window, cx)`       | Build and return the tooltip as AnyView      |\n\n### Built-in Tooltip Methods\n\nComponents with tooltip support typically provide these methods:\n\n| Method                                       | Description                             |\n| -------------------------------------------- | --------------------------------------- |\n| `tooltip(text)`                              | Add simple text tooltip                 |\n| `tooltip_with_action(text, action, context)` | Add tooltip with action keybinding      |\n| `tooltip(closure)`                           | Add custom tooltip with builder closure |\n\n### Tooltip Styling\n\nThe tooltip automatically applies theme-appropriate styling:\n\n- Background: `theme.popover`\n- Text color: `theme.popover_foreground`\n- Border: `theme.border`\n- Shadow: Medium drop shadow\n- Border radius: 6px\n- Font: System UI font\n\nYou can apply additional styling using the `Styled` trait:\n\n```rust\nTooltip::new(\"Custom styled tooltip\")\n    .bg(cx.theme().accent)\n    .text_color(cx.theme().accent_foreground)\n    .build(window, cx)\n```\n\n## Examples\n\n### Toolbar with Tooltips\n\n```rust\nh_flex()\n    .gap_1()\n    .child(\n        Button::new(\"new\")\n            .icon(IconName::Plus)\n            .tooltip_with_action(\"Create new file\", &NewFile, Some(\"Editor\"))\n    )\n    .child(\n        Button::new(\"open\")\n            .icon(IconName::FolderOpen)\n            .tooltip_with_action(\"Open file\", &OpenFile, Some(\"Editor\"))\n    )\n    .child(\n        Button::new(\"save\")\n            .icon(IconName::Save)\n            .tooltip_with_action(\"Save file\", &SaveFile, Some(\"Editor\"))\n    )\n```\n\n### Status Indicators with Tooltips\n\n```rust\nh_flex()\n    .gap_2()\n    .child(\n        div()\n            .size_3()\n            .rounded_full()\n            .bg(cx.theme().success)\n            .tooltip(|window, cx| {\n                Tooltip::new(\"Connected to server\").build(window, cx)\n            })\n    )\n    .child(\n        div()\n            .size_3()\n            .rounded_full()\n            .bg(cx.theme().warning)\n            .tooltip(|window, cx| {\n                Tooltip::new(\"Limited connectivity\").build(window, cx)\n            })\n    )\n```\n\n### Interactive Elements with Rich Tooltips\n\n```rust\nv_flex()\n    .gap_3()\n    .child(\n        div()\n            .p_2()\n            .border_1()\n            .border_color(cx.theme().border)\n            .rounded(cx.theme().radius)\n            .child(\"File: document.txt\")\n            .id(\"file-item\")\n            .tooltip(|window, cx| {\n                Tooltip::element(|_, cx| {\n                    v_flex()\n                        .gap_1()\n                        .child(\n                            h_flex()\n                                .gap_2()\n                                .child(IconName::File)\n                                .child(\"document.txt\")\n                                .text_sm()\n                                .font_medium()\n                        )\n                        .child(\n                            div()\n                                .child(\"Size: 2.4 KB\")\n                                .text_xs()\n                                .text_color(cx.theme().muted_foreground)\n                        )\n                        .child(\n                            div()\n                                .child(\"Modified: 2 hours ago\")\n                                .text_xs()\n                                .text_color(cx.theme().muted_foreground)\n                        )\n                        .child(\n                            h_flex()\n                                .gap_1()\n                                .child(Kbd::new(\"Enter\"))\n                                .child(\"to open\")\n                                .text_xs()\n                                .text_color(cx.theme().muted_foreground)\n                        )\n                })\n                .build(window, cx)\n            })\n    )\n```\n\n### Form Validation with Tooltips\n\n```rust\nstruct FormView {\n    email_error: Option<String>,\n    password_error: Option<String>,\n}\n\nv_flex()\n    .gap_4()\n    .child(\n        Input::new(\"email\")\n            .placeholder(\"Email address\")\n            .when_some(self.email_error.clone(), |this, error| {\n                this.tooltip(move |window, cx| {\n                    Tooltip::element(|_, cx| {\n                        h_flex()\n                            .gap_1()\n                            .child(IconName::AlertCircle)\n                            .child(error.clone())\n                            .text_color(cx.theme().destructive)\n                    })\n                    .build(window, cx)\n                })\n            })\n    )\n```\n\n## Best Practices\n\n### Content Guidelines\n\n- **Be concise**: Keep tooltip text short and to the point\n- **Be helpful**: Provide additional context, not redundant information\n- **Use proper tone**: Match your application's voice and tone\n- **Avoid critical info**: Don't put essential information only in tooltips\n\n### Usage Guidelines\n\n- **Progressive disclosure**: Use tooltips for additional context, not primary information\n- **Consistency**: Use consistent tooltip patterns throughout your application\n- **Performance**: Avoid complex content in frequently triggered tooltips\n- **Testing**: Test tooltips with both mouse and keyboard interaction\n\n### Examples of Good Tooltip Content\n\n```rust\n// Good: Provides helpful context\nButton::new(\"delete\")\n    .icon(IconName::Trash)\n    .tooltip(\"Delete this item permanently\")\n\n// Good: Explains abbreviation\ndiv()\n    .child(\"CPU: 45%\")\n    .tooltip(\"Central Processing Unit usage\")\n\n// Good: Describes action with keybinding\nButton::new(\"undo\")\n    .icon(IconName::Undo)\n    .tooltip_with_action(\"Undo last action\", &Undo, Some(\"Editor\"))\n```\n\n### Examples to Avoid\n\n```rust\n// Avoid: Redundant information\nButton::new(\"save\")\n    .label(\"Save\")\n    .tooltip(\"Save\") // Doesn't add value\n\n// Avoid: Critical information\nButton::new(\"delete\")\n    .tooltip(\"This will permanently delete all your files\") // Too important for tooltip only\n```\n"
  },
  {
    "path": "docs/docs/components/tree.md",
    "content": "---\ntitle: Tree\ndescription: A hierarchical tree view component for displaying and navigating tree-structured data.\n---\n\n# Tree\n\nA versatile tree component for displaying hierarchical data with expand/collapse functionality, keyboard navigation, and custom item rendering. Perfect for file explorers, navigation menus, or any nested data structure.\n\n## Import\n\n```rust\nuse gpui_component::tree::{tree, TreeState, TreeItem, TreeEntry};\n```\n\n## Usage\n\n### Basic Tree\n\n```rust\n// Create tree state\nlet tree_state = cx.new(|cx| {\n    TreeState::new(cx).items(vec![\n        TreeItem::new(\"src\", \"src\")\n            .expanded(true)\n            .child(TreeItem::new(\"src/lib.rs\", \"lib.rs\"))\n            .child(TreeItem::new(\"src/main.rs\", \"main.rs\")),\n        TreeItem::new(\"Cargo.toml\", \"Cargo.toml\"),\n        TreeItem::new(\"README.md\", \"README.md\"),\n    ])\n});\n\n// Render tree\ntree(&tree_state, |ix, entry, selected, window, cx| {\n    ListItem::new(ix)\n        .child(\n            h_flex()\n                .gap_2()\n                .child(entry.item().label.clone())\n        )\n})\n```\n\n### File Tree with Icons\n\n```rust\nuse gpui_component::{ListItem, IconName, h_flex};\n\ntree(&tree_state, |ix, entry, selected, window, cx| {\n    let item = entry.item();\n    let icon = if !entry.is_folder() {\n        IconName::File\n    } else if entry.is_expanded() {\n        IconName::FolderOpen\n    } else {\n        IconName::Folder\n    };\n\n    ListItem::new(ix)\n        .selected(selected)\n        .pl(px(16.) * entry.depth() + px(12.)) // Indent based on depth\n        .child(\n            h_flex()\n                .gap_2()\n                .child(icon)\n                .child(item.label.clone())\n        )\n        .on_click(cx.listener(move |_, _, _, _| {\n            // Handle item click\n        }))\n})\n```\n\n### Dynamic Tree Loading\n\n```rust\nimpl MyView {\n    fn load_files(&mut self, path: PathBuf, cx: &mut Context<Self>) {\n        let tree_state = self.tree_state.clone();\n        cx.spawn(async move |cx| {\n            let items = build_file_items(&path).await;\n            tree_state.update(cx, |state, cx| {\n                state.set_items(items, cx);\n            })\n        }).detach();\n    }\n}\n\nfn build_file_items(path: &Path) -> Vec<TreeItem> {\n    let mut items = Vec::new();\n    if let Ok(entries) = std::fs::read_dir(path) {\n        for entry in entries.flatten() {\n            let path = entry.path();\n            let name = path.file_name()\n                .and_then(|n| n.to_str())\n                .unwrap_or(\"Unknown\")\n                .to_string();\n\n            if path.is_dir() {\n                let children = build_file_items(&path);\n                items.push(TreeItem::new(path.to_string_lossy(), name)\n                    .children(children));\n            } else {\n                items.push(TreeItem::new(path.to_string_lossy(), name));\n            }\n        }\n    }\n    items\n}\n```\n\n### Tree with Selection Handling\n\n```rust\nstruct MyTreeView {\n    tree_state: Entity<TreeState>,\n    selected_item: Option<TreeItem>,\n}\n\nimpl MyTreeView {\n    fn handle_selection(&mut self, item: TreeItem, cx: &mut Context<Self>) {\n        self.selected_item = Some(item.clone());\n        println!(\"Selected: {} ({})\", item.label, item.id);\n        cx.notify();\n    }\n}\n\n// In render method\ntree(&self.tree_state, {\n    let view = cx.entity();\n    move |ix, entry, selected, window, cx| {\n        view.update(cx, |this, cx| {\n            ListItem::new(ix)\n                .selected(selected)\n                .child(entry.item().label.clone())\n                .on_click(cx.listener({\n                    let item = entry.item().clone();\n                    move |this, _, _, cx| {\n                        this.handle_selection(item.clone(), cx);\n                    }\n                }))\n        })\n    }\n})\n```\n\n### Disabled Items\n\n```rust\nTreeItem::new(\"protected\", \"Protected Folder\")\n    .disabled(true)\n    .child(TreeItem::new(\"secret.txt\", \"secret.txt\"))\n```\n\n### Programmatic Tree Control\n\n```rust\n// Get current selection\nif let Some(entry) = tree_state.read(cx).selected_entry() {\n    println!(\"Current selection: {}\", entry.item().label);\n}\n\n// Set selection programmatically (by selected_index)\ntree_state.update(cx, |state, cx| {\n    state.set_selected_index(Some(2), cx); // Select third item\n});\n\n// Set selection programmatically (by tree item)\ntree_state.update(cx, |state, cx| {\n    state.set_selected_item(Some(item), cx); // Select third item\n});\n\n// Scroll to specific item\ntree_state.update(cx, |state, _| {\n    state.scroll_to_item(5, gpui::ScrollStrategy::Center);\n});\n\n// Clear selection (by selected_index)\ntree_state.update(cx, |state, cx| {\n    state.set_selected_index(None, cx);\n});\n\n// Clear selection (by tree item)\ntree_state.update(cx, |state, cx| {\n    state.set_selected_item(None, cx);\n});\n```\n\n## API Reference\n\n### TreeState\n\n| Method                         | Description                      |\n|--------------------------------|----------------------------------|\n| `new(cx)`                      | Create a new tree state          |\n| `items(items)`                 | Set initial tree items           |\n| `set_items(items, cx)`         | Update tree items and notify     |\n| `selected_index()`             | Get currently selected index     |\n| `set_selected_index(ix, cx)`   | Set selected index               |\n| `set_selected_item(item, cx)`  | Set selected by tree item        |\n| `selected_item(item, cx)`      | Get currently selected tree item |\n| `selected_entry()`             | Get currently selected entry     |\n| `scroll_to_item(ix, strategy)` | Scroll to specific item          |\n\n### TreeItem\n\n| Method            | Description                            |\n| ----------------- | -------------------------------------- |\n| `new(id, label)`  | Create new tree item with ID and label |\n| `child(item)`     | Add single child item                  |\n| `children(items)` | Add multiple child items               |\n| `expanded(bool)`  | Set expanded state                     |\n| `disabled(bool)`  | Set disabled state                     |\n| `is_folder()`     | Check if item has children             |\n| `is_expanded()`   | Check if item is expanded              |\n| `is_disabled()`   | Check if item is disabled              |\n\n### TreeEntry\n\n| Method          | Description                 |\n| --------------- | --------------------------- |\n| `item()`        | Get the source TreeItem     |\n| `depth()`       | Get item depth in tree      |\n| `is_folder()`   | Check if entry has children |\n| `is_expanded()` | Check if entry is expanded  |\n| `is_disabled()` | Check if entry is disabled  |\n\n### tree() Function\n\n| Parameter     | Description                           |\n| ------------- | ------------------------------------- |\n| `state`       | `Entity<TreeState>` for managing tree |\n| `render_item` | Closure for rendering each item       |\n\n#### Render Item Closure\n\n```rust\nFn(usize, &TreeEntry, bool, &mut Window, &mut App) -> ListItem\n```\n\n- `usize`: Item index in flattened tree\n- `&TreeEntry`: Tree entry with item and metadata\n- `bool`: Whether item is currently selected\n- `&mut Window`: Current window context\n- `&mut App`: Application context\n- Returns: `ListItem` for rendering\n\n## Examples\n\n### Lazy Loading Tree\n\n```rust\nstruct LazyTreeView {\n    tree_state: Entity<TreeState>,\n    loaded_paths: HashSet<String>,\n}\n\nimpl LazyTreeView {\n    fn load_children(&mut self, item_id: &str, cx: &mut Context<Self>) {\n        if self.loaded_paths.contains(item_id) {\n            return;\n        }\n\n        let path = PathBuf::from(item_id);\n        if path.is_dir() {\n            let tree_state = self.tree_state.clone();\n            let item_id = item_id.to_string();\n\n            cx.spawn(async move |cx| {\n                let children = load_directory_children(&path).await;\n                tree_state.update(cx, |state, cx| {\n                    // Update specific item with loaded children\n                    state.update_item_children(&item_id, children, cx);\n                })\n            }).detach();\n\n            self.loaded_paths.insert(item_id.to_string());\n        }\n    }\n}\n```\n\n### Search and Filter\n\n```rust\nstruct SearchableTree {\n    tree_state: Entity<TreeState>,\n    original_items: Vec<TreeItem>,\n    search_query: String,\n}\n\nimpl SearchableTree {\n    fn filter_tree(&mut self, query: &str, cx: &mut Context<Self>) {\n        self.search_query = query.to_string();\n\n        let filtered_items = if query.is_empty() {\n            self.original_items.clone()\n        } else {\n            filter_tree_items(&self.original_items, query)\n        };\n\n        self.tree_state.update(cx, |state, cx| {\n            state.set_items(filtered_items, cx);\n        });\n    }\n}\n\nfn filter_tree_items(items: &[TreeItem], query: &str) -> Vec<TreeItem> {\n    items.iter()\n        .filter_map(|item| {\n            if item.label.to_lowercase().contains(&query.to_lowercase()) {\n                Some(item.clone().expanded(true)) // Auto-expand matches\n            } else {\n                // Check if any children match\n                let filtered_children = filter_tree_items(&item.children, query);\n                if !filtered_children.is_empty() {\n                    Some(item.clone()\n                        .children(filtered_children)\n                        .expanded(true))\n                } else {\n                    None\n                }\n            }\n        })\n        .collect()\n}\n```\n\n### Multi-Select Tree\n\n```rust\nstruct MultiSelectTree {\n    tree_state: Entity<TreeState>,\n    selected_items: HashSet<String>,\n}\n\nimpl MultiSelectTree {\n    fn toggle_selection(&mut self, item_id: &str, cx: &mut Context<Self>) {\n        if self.selected_items.contains(item_id) {\n            self.selected_items.remove(item_id);\n        } else {\n            self.selected_items.insert(item_id.to_string());\n        }\n        cx.notify();\n    }\n\n    fn is_selected(&self, item_id: &str) -> bool {\n        self.selected_items.contains(item_id)\n    }\n}\n\n// In render method\ntree(&self.tree_state, {\n    let view = cx.entity();\n    move |ix, entry, _selected, window, cx| {\n        view.update(cx, |this, cx| {\n            let item = entry.item();\n            let is_multi_selected = this.is_selected(&item.id);\n\n            ListItem::new(ix)\n                .selected(is_multi_selected)\n                .child(\n                    h_flex()\n                        .gap_2()\n                        .child(checkbox().checked(is_multi_selected))\n                        .child(item.label.clone())\n                )\n                .on_click(cx.listener({\n                    let item_id = item.id.clone();\n                    move |this, _, _, cx| {\n                        this.toggle_selection(&item_id, cx);\n                    }\n                }))\n        })\n    }\n})\n```\n\n## Keyboard Navigation\n\nThe Tree component supports comprehensive keyboard navigation:\n\n| Key     | Action                                    |\n| ------- | ----------------------------------------- |\n| `↑`     | Select previous item                      |\n| `↓`     | Select next item                          |\n| `←`     | Collapse current folder or move to parent |\n| `→`     | Expand current folder                     |\n| `Enter` | Toggle expand/collapse for folders        |\n| `Space` | Custom action (configurable)              |\n\n```rust\n// Custom keyboard handling\ntree(&tree_state)\n    .key_context(\"MyTree\")\n    .on_action(cx.listener(|this, action: &MyCustomAction, _, cx| {\n        // Handle custom actions\n    }))\n```\n"
  },
  {
    "path": "docs/docs/components/virtual-list.md",
    "content": "---\ntitle: VirtualList\ndescription: High-performance virtualized list component for rendering large datasets with variable item sizes.\n---\n\n# VirtualList\n\nVirtualList is a high-performance component designed for efficiently rendering large datasets by only rendering visible items. Unlike uniform lists, VirtualList supports variable item sizes, making it perfect for complex layouts like tables with different row heights or dynamic content.\n\n## Import\n\n```rust\nuse gpui_component::{\n    v_virtual_list, h_virtual_list, VirtualListScrollHandle,\n    scroll::{Scrollbar, ScrollbarState, ScrollbarAxis},\n};\nuse std::rc::Rc;\nuse gpui::{px, size, ScrollStrategy, Size, Pixels};\n```\n\n## Usage\n\n### Basic Vertical Virtual List\n\n```rust\nuse std::rc::Rc;\nuse gpui::{px, size, Size, Pixels};\n\npub struct ListViewExample {\n    items: Vec<String>,\n    item_sizes: Rc<Vec<Size<Pixels>>>,\n    scroll_handle: VirtualListScrollHandle,\n}\n\nimpl ListViewExample {\n    fn new(cx: &mut Context<Self>) -> Self {\n        let items = (0..5000).map(|i| format!(\"Item {}\", i)).collect::<Vec<_>>();\n        let item_sizes = Rc::new(items.iter().map(|_| size(px(200.), px(30.))).collect());\n\n        Self {\n            items,\n            item_sizes,\n            scroll_handle: VirtualListScrollHandle::new(),\n        }\n    }\n}\n\nimpl Render for ListViewExample {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_virtual_list(\n            cx.entity().clone(),\n            \"my-list\",\n            self.item_sizes.clone(),\n            |view, visible_range, _, cx| {\n                visible_range\n                    .map(|ix| {\n                        div()\n                            .h(px(30.))\n                            .w_full()\n                            .bg(cx.theme().secondary)\n                            .child(format!(\"Item {}\", ix))\n                    })\n                    .collect()\n            },\n        )\n        .track_scroll(&self.scroll_handle)\n    }\n}\n```\n\n### Horizontal Virtual List\n\n```rust\nh_virtual_list(\n    cx.entity().clone(),\n    \"horizontal-list\",\n    item_sizes.clone(),\n    |view, visible_range, _, cx| {\n        visible_range\n            .map(|ix| {\n                div()\n                    .w(px(120.))  // Width is used for horizontal lists\n                    .h_full()\n                    .bg(cx.theme().accent)\n                    .child(format!(\"Card {}\", ix))\n            })\n            .collect()\n    },\n)\n.track_scroll(&scroll_handle)\n```\n\n### Variable Item Sizes\n\nVirtualList excels at handling items with different sizes:\n\n```rust\nlet item_sizes = Rc::new(\n    (0..1000)\n        .map(|i| {\n            // Different heights based on index\n            let height = if i % 5 == 0 {\n                px(60.)  // Header items are taller\n            } else if i % 3 == 0 {\n                px(45.)  // Some items are medium\n            } else {\n                px(30.)  // Regular items\n            };\n            size(px(300.), height)\n        })\n        .collect::<Vec<_>>()\n);\n\nv_virtual_list(\n    cx.entity().clone(),\n    \"variable-list\",\n    item_sizes.clone(),\n    |view, visible_range, _, cx| {\n        visible_range\n            .map(|ix| {\n                let content = if ix % 5 == 0 {\n                    format!(\"Header {}\", ix / 5)\n                } else {\n                    format!(\"Item {}\", ix)\n                };\n\n                let bg_color = if ix % 5 == 0 {\n                    cx.theme().accent\n                } else {\n                    cx.theme().secondary\n                };\n\n                div()\n                    .w_full()\n                    .h(item_sizes[ix].height)\n                    .bg(bg_color)\n                    .flex()\n                    .items_center()\n                    .px_4()\n                    .child(content)\n            })\n            .collect()\n    },\n)\n```\n\n### Table-like Layout with Multiple Columns\n\nVirtualList can render complex layouts like tables:\n\n```rust\nv_virtual_list(\n    cx.entity().clone(),\n    \"table-list\",\n    item_sizes.clone(),\n    |view, visible_range, _, cx| {\n        visible_range\n            .map(|row_ix| {\n                h_flex()\n                    .w_full()\n                    .h(px(40.))\n                    .border_b_1()\n                    .border_color(cx.theme().border)\n                    .children(\n                        // Multiple columns per row\n                        (0..5).map(|col_ix| {\n                            div()\n                                .flex_1()\n                                .h_full()\n                                .px_3()\n                                .flex()\n                                .items_center()\n                                .child(format!(\"R{}C{}\", row_ix, col_ix))\n                        })\n                    )\n            })\n            .collect()\n    },\n)\n```\n\n## Scroll Handling\n\n### Basic Scroll Control\n\n```rust\npub struct ScrollableList {\n    scroll_handle: VirtualListScrollHandle,\n    scroll_state: ScrollbarState,\n}\n\nimpl Render for ScrollableList {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .relative()\n            .size_full()\n            .child(\n                v_virtual_list(/* ... */)\n                    .track_scroll(&self.scroll_handle)\n                    .p_4()\n                    .border_1()\n                    .border_color(cx.theme().border)\n            )\n            .child(\n                // Add scrollbars\n                div()\n                    .absolute()\n                    .top_0()\n                    .left_0()\n                    .right_0()\n                    .bottom_0()\n                    .child(\n                        Scrollbar::both(&self.scroll_state, &self.scroll_handle)\n                            .axis(ScrollbarAxis::Vertical)\n                    )\n            )\n    }\n}\n```\n\n### Programmatic Scrolling\n\n```rust\nimpl ScrollableList {\n    // Scroll to specific item\n    fn scroll_to_item(&self, index: usize) {\n        self.scroll_handle.scroll_to_item(index, ScrollStrategy::Top);\n    }\n\n    // Center item in view\n    fn center_item(&self, index: usize) {\n        self.scroll_handle.scroll_to_item(index, ScrollStrategy::Center);\n    }\n\n    // Scroll to bottom\n    fn scroll_to_bottom(&self) {\n        self.scroll_handle.scroll_to_bottom();\n    }\n\n    // Get current scroll position\n    fn get_scroll_offset(&self) -> Point<Pixels> {\n        self.scroll_handle.offset()\n    }\n\n    // Set scroll position manually\n    fn set_scroll_position(&self, offset: Point<Pixels>) {\n        self.scroll_handle.set_offset(offset);\n    }\n}\n```\n\n### Both Axis Scrolling\n\nFor content that scrolls in both directions:\n\n```rust\nv_virtual_list(\n    cx.entity().clone(),\n    \"both-axis\",\n    item_sizes.clone(),\n    |view, visible_range, _, cx| {\n        visible_range\n            .map(|ix| {\n                // Wide content that requires horizontal scrolling\n                h_flex()\n                    .gap_2()\n                    .children((0..20).map(|col| {\n                        div()\n                            .min_w(px(100.))\n                            .h(px(30.))\n                            .bg(cx.theme().secondary)\n                            .child(format!(\"R{}C{}\", ix, col))\n                    }))\n            })\n            .collect()\n    },\n)\n.track_scroll(&scroll_handle)\n.child(\n    Scrollbar::both(&scroll_state, &scroll_handle)\n        .axis(ScrollbarAxis::Both)\n)\n```\n\n## Performance Optimization\n\n### Efficient Item Rendering\n\nOnly visible items are rendered, making VirtualList highly performant:\n\n```rust\n// The render function is only called for visible items\nv_virtual_list(\n    cx.entity().clone(),\n    \"efficient-list\",\n    item_sizes.clone(),\n    |view, visible_range, _, cx| {\n        // visible_range contains only the items currently visible\n        // This typically contains 10-20 items, not all 10,000\n        println!(\"Rendering {} items out of {}\",\n                visible_range.len(),\n                view.total_items);\n\n        visible_range\n            .map(|ix| {\n                // Complex rendering logic here\n                // Only executed for visible items\n                expensive_item_renderer(ix, cx)\n            })\n            .collect()\n    },\n)\n```\n\n### Memory Management\n\nVirtualList automatically manages memory by:\n\n- Only rendering visible items\n- Reusing rendered elements when scrolling\n- Calculating precise visible ranges\n\n```rust\n// Large dataset - only visible items use memory\nlet large_dataset = (0..1_000_000).map(|i| format!(\"Item {}\", i)).collect();\n\n// Memory usage remains constant regardless of dataset size\nv_virtual_list(/* render only visible items */)\n```\n\n### Variable Heights with Caching\n\nFor dynamic content with calculated heights:\n\n```rust\nstruct DynamicItem {\n    content: String,\n    calculated_height: Option<Pixels>,\n}\n\nimpl MyView {\n    fn calculate_item_size(&mut self, ix: usize) -> Size<Pixels> {\n        if let Some(height) = self.items[ix].calculated_height {\n            return size(px(300.), height);\n        }\n\n        // Calculate height based on content\n        let content_lines = self.items[ix].content.lines().count();\n        let height = px(20. + content_lines as f32 * 16.);\n\n        // Cache the calculated height\n        self.items[ix].calculated_height = Some(height);\n\n        size(px(300.), height)\n    }\n}\n```\n\n## Examples\n\n### File Explorer with Virtual Scrolling\n\n```rust\npub struct FileExplorer {\n    files: Vec<FileEntry>,\n    item_sizes: Rc<Vec<Size<Pixels>>>,\n    scroll_handle: VirtualListScrollHandle,\n    selected_index: Option<usize>,\n}\n\nimpl FileExplorer {\n    fn calculate_item_heights(&mut self) {\n        let sizes = self.files.iter().map(|file| {\n            // Different heights for different file types\n            let height = match file.file_type {\n                FileType::Directory => px(40.),\n                FileType::Image => px(60.),  // Larger for thumbnails\n                FileType::Document => px(35.),\n                _ => px(30.),\n            };\n            size(px(400.), height)\n        }).collect();\n\n        self.item_sizes = Rc::new(sizes);\n    }\n}\n\nimpl Render for FileExplorer {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_virtual_list(\n            cx.entity().clone(),\n            \"file-list\",\n            self.item_sizes.clone(),\n            |view, visible_range, _, cx| {\n                visible_range\n                    .map(|ix| {\n                        let file = &view.files[ix];\n                        let is_selected = view.selected_index == Some(ix);\n\n                        div()\n                            .w_full()\n                            .h(view.item_sizes[ix].height)\n                            .px_3()\n                            .py_1()\n                            .flex()\n                            .items_center()\n                            .gap_2()\n                            .bg(if is_selected {\n                                cx.theme().accent\n                            } else {\n                                Color::transparent()\n                            })\n                            .hover(|style| style.bg(cx.theme().secondary_hover))\n                            .child(file_icon(&file.file_type))\n                            .child(file.name.clone())\n                            .child(\n                                div()\n                                    .flex_1()\n                                    .text_right()\n                                    .text_xs()\n                                    .text_color(cx.theme().muted_foreground)\n                                    .child(format_file_size(file.size))\n                            )\n                            .on_click(cx.listener(move |view, _, _, cx| {\n                                view.selected_index = Some(ix);\n                                cx.notify();\n                            }))\n                    })\n                    .collect()\n            },\n        )\n        .track_scroll(&self.scroll_handle)\n    }\n}\n```\n\n### Chat Messages with Auto-scroll\n\n```rust\npub struct ChatWindow {\n    messages: Vec<ChatMessage>,\n    scroll_handle: VirtualListScrollHandle,\n    auto_scroll: bool,\n}\n\nimpl ChatWindow {\n    fn add_message(&mut self, message: ChatMessage, cx: &mut Context<Self>) {\n        self.messages.push(message);\n\n        // Recalculate item sizes\n        self.update_item_sizes();\n\n        if self.auto_scroll {\n            // Scroll to bottom for new messages\n            self.scroll_handle.scroll_to_bottom();\n        }\n\n        cx.notify();\n    }\n\n    fn update_item_sizes(&mut self) {\n        let sizes = self.messages.iter().map(|msg| {\n            // Calculate height based on message content\n            let lines = msg.content.lines().count().max(1);\n            let height = px(40. + (lines.saturating_sub(1)) as f32 * 16.);\n            size(px(350.), height)\n        }).collect();\n\n        self.item_sizes = Rc::new(sizes);\n    }\n}\n\nimpl Render for ChatWindow {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .size_full()\n            .child(\n                v_virtual_list(\n                    cx.entity().clone(),\n                    \"chat-messages\",\n                    self.item_sizes.clone(),\n                    |view, visible_range, _, cx| {\n                        visible_range\n                            .map(|ix| {\n                                let msg = &view.messages[ix];\n\n                                div()\n                                    .w_full()\n                                    .px_4()\n                                    .py_2()\n                                    .child(\n                                        v_flex()\n                                            .gap_1()\n                                            .child(\n                                                h_flex()\n                                                    .justify_between()\n                                                    .child(\n                                                        div()\n                                                            .text_sm()\n                                                            .font_weight(FontWeight::SEMIBOLD)\n                                                            .child(msg.author.clone())\n                                                    )\n                                                    .child(\n                                                        div()\n                                                            .text_xs()\n                                                            .text_color(cx.theme().muted_foreground)\n                                                            .child(format_timestamp(msg.timestamp))\n                                                    )\n                                            )\n                                            .child(\n                                                div()\n                                                    .text_sm()\n                                                    .child(msg.content.clone())\n                                            )\n                                    )\n                            })\n                            .collect()\n                    },\n                )\n                .track_scroll(&self.scroll_handle)\n                .flex_1()\n            )\n            .child(\n                // Chat input at bottom\n                div()\n                    .w_full()\n                    .h(px(60.))\n                    .border_t_1()\n                    .border_color(cx.theme().border)\n                    .child(\"Chat input here...\")\n            )\n    }\n}\n```\n\n### Data Grid with Fixed Headers\n\n```rust\npub struct DataGrid {\n    headers: Vec<String>,\n    data: Vec<Vec<String>>,\n    column_widths: Vec<Pixels>,\n    scroll_handle: VirtualListScrollHandle,\n}\n\nimpl Render for DataGrid {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .size_full()\n            .child(\n                // Fixed header\n                h_flex()\n                    .w_full()\n                    .h(px(40.))\n                    .bg(cx.theme().secondary)\n                    .border_b_1()\n                    .border_color(cx.theme().border)\n                    .children(\n                        self.headers.iter().zip(&self.column_widths).map(|(header, &width)| {\n                            div()\n                                .w(width)\n                                .h_full()\n                                .px_3()\n                                .flex()\n                                .items_center()\n                                .font_weight(FontWeight::SEMIBOLD)\n                                .child(header.clone())\n                        })\n                    )\n            )\n            .child(\n                // Virtual list for data rows\n                v_virtual_list(\n                    cx.entity().clone(),\n                    \"data-rows\",\n                    Rc::new(vec![size(px(800.), px(32.)); self.data.len()]),\n                    |view, visible_range, _, cx| {\n                        visible_range\n                            .map(|row_ix| {\n                                h_flex()\n                                    .w_full()\n                                    .h(px(32.))\n                                    .border_b_1()\n                                    .border_color(cx.theme().border.opacity(0.5))\n                                    .children(\n                                        view.data[row_ix].iter().zip(&view.column_widths).map(|(cell, &width)| {\n                                            div()\n                                                .w(width)\n                                                .h_full()\n                                                .px_3()\n                                                .flex()\n                                                .items_center()\n                                                .child(cell.clone())\n                                        })\n                                    )\n                            })\n                            .collect()\n                    },\n                )\n                .track_scroll(&self.scroll_handle)\n                .flex_1()\n            )\n    }\n}\n```\n\n## Best Practices\n\n1. **Item Sizing**: Pre-calculate item sizes when possible for best performance\n2. **Memory Management**: Use VirtualList for any list with >50 items\n3. **Scroll Performance**: Avoid heavy computations in render functions\n4. **State Management**: Keep item state separate from rendering logic\n5. **Error Handling**: Handle edge cases like empty lists gracefully\n6. **Testing**: Test with various data sizes and scroll positions\n\n## Performance Tips\n\n1. **Pre-calculate Sizes**: Calculate item sizes upfront rather than during render\n2. **Minimize Re-renders**: Use stable item keys and avoid recreating render functions\n3. **Batch Updates**: Group multiple data changes together\n4. **Efficient Rendering**: Keep item render functions lightweight\n5. **Memory Monitoring**: Monitor memory usage with very large datasets\n"
  },
  {
    "path": "docs/docs/context.md",
    "content": "---\ntitle: Context\ndescription: Learn about the Window and Context in GPUI.\norder: -4\n---\n\nThe [Window], [App], [Context] and [Entity] are most important things in GPUI, it appears everywhere.\n\n- [Window] - The current window instance, which for handle the **Window Level** things.\n- [App] - The current application instance, which for handle the **Application Level** things.\n- [Context] - The Entity Context instance, which for handle the **Context Level** things.\n- [Entity] - The Entity instance, which for handle the **Entity Level** things.\n\nFor example:\n\n```rs\nfn new(window: &mut Window, cx: &mut App) {}\n\nimpl RenderOnce for MyElement {\n    fn render(self, window: &mut Window, cx: &mut App) {}\n}\n\nimpl Render for MyView {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) {}\n}\n```\n\n:::info\nAs you can see, we always use `cx` to represent `App` and `Context<Self>`,\nwhich is the standard naming convention for GPUI,\nwe can follow this convention to make our code more readable and maintainable.\n:::\n\n[Window]: https://docs.rs/gpui/latest/gpui/struct.Window.html\n[App]: https://docs.rs/gpui/latest/gpui/struct.App.html\n[Context]: https://docs.rs/gpui/latest/gpui/struct.Context.html\n[Entity]: https://docs.rs/gpui/latest/gpui/struct.Entity.html\n"
  },
  {
    "path": "docs/docs/element_id.md",
    "content": "---\ntitle: ElementId\ndescription: To introduce the ElementId concept in GPUI.\norder: -4\n---\n\nThe [ElementId] is a unique identifier for a GPUI element. It is used to reference elements in the GPUI component tree.\n\nBefore you start using GPUI and GPUI Component, you need to understand the [ElementId].\n\nFor example:\n\n```rs\ndiv().id(\"my-element\").child(\"Hello, World!\")\n```\n\nIn this case, the `div` element has an `id` of `\"my-element\"`. The add `id` is used for GPUI for binding events, for example `on_click` or `on_mouse_move`, the `element` with `id` in GPUI we call [Stateful\\<E\\>].\n\nWe also use `id` (actually, it uses [GlobalElementId] internally in GPUI) to manage the `state` in some elements, by using `window.use_keyed_state`, so it is important to keep the `id` unique.\n\n## Unique\n\nThe `id` should be unique within the layout scope (In a same [Stateful\\<E\\>] parent).\n\nFor example we have a list with multiple items:\n\n```rs\ndiv().id(\"app\").child(\n    div().id(\"list1\").child(vec![\n        div().id(1).child(\"Item 1\"),\n        div().id(2).child(\"Item 2\"),\n        div().id(3).child(\"Item 3\"),\n    ])\n).child(\n    div().id(\"list2\").child(vec![\n        div().id(1).child(\"Item 1\"),\n    ])\n)\n```\n\nIn this case, we can named the child items with a very simple id, because they are have a parent `list1` element with an `id`.\n\nGPUI internal will generate [GlobalElementId] with the parent elements's `id`, in this example, the `Item 1` will have global_id:\n\n```rs\n[\"app\", \"list1\", 1]\n```\n\nAnd the `Item 1` in `list2` will have global_id:\n\n```rs\n[\"app\", \"list2\", 1]\n```\n\nSo we can named the child items with a very simple id.\n\n[ElementId]: https://docs.rs/gpui/latest/gpui/enum.ElementId.html\n[GlobalElementId]: https://docs.rs/gpui/latest/gpui/struct.GlobalElementId.html\n[Stateful]: https://docs.rs/gpui/latest/gpui/struct.Stateful.html\n[Stateful\\<E\\>]: https://docs.rs/gpui/latest/gpui/struct.Stateful.html\n"
  },
  {
    "path": "docs/docs/getting-started.md",
    "content": "---\ntitle: Getting Started\ndescription: Learn how to set up and use GPUI Component in your project\norder: -2\n---\n\n# Getting Started\n\n## Installation\n\nAdd dependencies to your `Cargo.toml`:\n\n```toml-vue\n[dependencies]\ngpui = \"{{ GPUI_VERSION }}\"\ngpui-component = \"{{ VERSION }}\"\n# Optional, for default bundled assets\ngpui-component-assets = \"{{ VERSION }}\"\nanyhow = \"1.0\"\n```\n\n:::tip\nThe `gpui-component-assets` crate is optional.\n\nIt provides a default set of icon assets. If you want to manage your own assets, you can skip adding this dependency.\n\nSee [Icons & Assets](./assets.md) for more details.\n:::\n\n## Quick Start\n\nHere's a simple example to get you started:\n\n```rust\nuse gpui::*;\nuse gpui_component::{button::*, *};\n\npub struct HelloWorld;\n\nimpl Render for HelloWorld {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .v_flex()\n            .gap_2()\n            .size_full()\n            .items_center()\n            .justify_center()\n            .child(\"Hello, World!\")\n            .child(\n                Button::new(\"ok\")\n                    .primary()\n                    .label(\"Let's Go!\")\n                    .on_click(|_, _, _| println!(\"Clicked!\")),\n            )\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application().with_assets(gpui_component_assets::Assets);\n\n    app.run(move |cx| {\n        // This must be called before using any GPUI Component features.\n        gpui_component::init(cx);\n\n        cx.spawn(async move |cx| {\n            cx.open_window(WindowOptions::default(), |window, cx| {\n                let view = cx.new(|_| HelloWorld);\n                // This first level on the window, should be a Root.\n                cx.new(|cx| Root::new(view, window, cx))\n            })\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n```\n\n:::info\nMake sure to call `gpui_component::init(cx);` at first line inside the `app.run` closure. This initializes the GPUI Component system.\n\nThis is required for theming and other global settings to work correctly.\n:::\n\n## Basic Concepts\n\n### Stateless Elements\n\nGPUI Component uses stateless [RenderOnce] elements, making them simple and predictable. State management is handled at the view level, not in individual components.\n\nThe are all implemented [IntoElement] types.\n\nFor example:\n\n```rs\nstruct MyView;\n\nimpl Render for MyView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .child(Button::new(\"btn\").label(\"Click Me\"))\n            .child(Tag::secondary().child(\"Secondary\"))\n    }\n}\n```\n\n### Stateful Components\n\nThere are some stateful components like `Dropdown`, `List`, and `Table` that manage their own internal state for convenience, these components implement the [Render] trait.\n\nThose components to use are a bit different, we need create the [Entity] and hold it in the view struct.\n\n```rs\nstruct MyView {\n    input: Entity<InputState>,\n}\n\nimpl MyView {\n    fn new(window: &Window, cx: &mut Context<Self>) -> Self {\n        let input = cx.new(|cx| InputState::new(window, cx).default_value(\"Hello 世界\"));\n        Self { input }\n    }\n}\n\nimpl Render for MyView {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        self.input.clone()\n    }\n}\n```\n\n### Theming\n\nAll components support theming through the built-in `Theme` system:\n\n```rust\nuse gpui_component::{ActiveTheme, Theme};\n\n// Access theme colors in your components\ncx.theme().primary\ncx.theme().background\ncx.theme().foreground\n```\n\n### Sizing\n\nMost components support multiple sizes:\n\n```rust\nButton::new(\"btn\").small()\nButton::new(\"btn\").medium() // default\nButton::new(\"btn\").large()\nButton::new(\"btn\").xsmall()\n```\n\n### Variants\n\nComponents offer different visual variants:\n\n```rust\nButton::new(\"btn\").primary()\nButton::new(\"btn\").danger()\nButton::new(\"btn\").warning()\nButton::new(\"btn\").success()\nButton::new(\"btn\").ghost()\nButton::new(\"btn\").outline()\n```\n\n## Icons\n\n:::info\nIcons are not bundled with GPUI Component to keep the library lightweight.\n\nContinue read [Icons & Assets](./assets.md) to learn how to add icons to your project.\n:::\n\nGPUI Component has an `Icon` element, but does not include SVG files by default.\n\nThe examples use [Lucide](https://lucide.dev) icons. You can use any icons you like by naming the SVG files as defined in `IconName`. Add the icons you need to your project.\n\n```rust\nuse gpui_component::{Icon, IconName};\n\nIcon::new(IconName::Check)\nIcon::new(IconName::Search).small()\n```\n\n## Next Steps\n\nExplore the component documentation to learn more about each component:\n\n- [Button](./components/button) - Interactive button component\n- [Input](./components/input) - Text input with validation\n- [Dialog](./components/dialog) - Dialog and modal windows\n- [DataTable](./components/data-table) - High-performance data tables\n- [More components...](./components/index)\n\n## Development\n\nTo run the component gallery:\n\n```bash\ncargo run\n```\n\nMore examples can be found in the `examples` directory:\n\n```bash\ncargo run --example <example_name>\n```\n\n[RenderOnce]: https://docs.rs/gpui/latest/gpui/trait.RenderOnce.html\n[IntoElement]: https://docs.rs/gpui/latest/gpui/trait.IntoElement.html\n[Render]: https://docs.rs/gpui/latest/gpui/trait.Render.html\n"
  },
  {
    "path": "docs/docs/index.md",
    "content": "---\ntitle: Introduction\ndescription: Rust GUI components for building fantastic cross-platform desktop application by using GPUI.\n---\n\n# GPUI Component Introduction\n\nGPUI Component is a Rust UI component library for building fantastic desktop applications using [GPUI](https://gpui.rs).\n\nGPUI Component is a comprehensive UI component library for building fantastic desktop applications using [GPUI](https://gpui.rs). It provides 60+ cross-platform components with modern design, theming support, and high performance.\n\n## Features\n\n- **Richness**: 60+ cross-platform desktop UI components\n- **Native**: Inspired by macOS and Windows controls, combined with shadcn/ui design\n- **Ease of Use**: Stateless `RenderOnce` components, simple and user-friendly\n- **Customizable**: Built-in `Theme` and `ThemeColor`, supporting multi-theme\n- **Versatile**: Supports sizes like `xs`, `sm`, `md`, and `lg`\n- **Flexible Layout**: Dock layout for panel arrangements, resizing, and freeform (Tiles) layouts\n- **High Performance**: Virtualized Table and List components for smooth large-data rendering\n- **Content Rendering**: Native support for Markdown and simple HTML\n- **Charting**: Built-in charts for visualization\n- **Editor**: High performance code editor with LSP support\n- **Syntax Highlighting**: Using Tree Sitter\n\n## Quick Example\n\nAdd `gpui` and `gpui-component` to your `Cargo.toml`:\n\n```toml-vue\n[dependencies]\ngpui = \"{{ VERSION }}\"\ngpui-component = \"{{ VERSION }}\"\n```\n\nThen create a simple \"Hello, World!\" application with a button:\n\n```rust\nuse gpui::*;\nuse gpui_component::{button::*, *};\n\npub struct HelloWorld;\nimpl Render for HelloWorld {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .v_flex()\n            .gap_2()\n            .size_full()\n            .items_center()\n            .justify_center()\n            .child(\"Hello, World!\")\n            .child(\n                Button::new(\"ok\")\n                    .primary()\n                    .label(\"Let's Go!\")\n                    .on_click(|_, _, _| println!(\"Clicked!\")),\n            )\n    }\n}\n\nfn main() {\n    let app = Application::new();\n\n    app.run(move |cx| {\n        // This must be called before using any GPUI Component features.\n        gpui_component::init(cx);\n\n        cx.spawn(async move |cx| {\n            cx.open_window(WindowOptions::default(), |window, cx| {\n                let view = cx.new(|_| HelloWorld);\n                // This first level on the window, should be a Root.\n                cx.new(|cx| Root::new(view, window, cx))\n            })\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n```\n\n## Community & Support\n\n- [GitHub Repository](https://github.com/longbridge/gpui-component)\n- [Issue Tracker](https://github.com/longbridge/gpui-component/issues)\n- [Contributing Guide](https://github.com/longbridge/gpui-component/blob/main/CONTRIBUTING.md)\n\n## License\n\nApache-2.0\n"
  },
  {
    "path": "docs/docs/installation.md",
    "content": "---\ntitle: Installation\norder: -1\n---\n\n# Installation\n\nBefore you start to build your application with `gpui-component`, you need to install the library.\n\n## System Requirements\n\nWe can development application on macOS, Windows or Linux.\n\n### macOS\n\n- macOS 15 or later\n- Xcode command line tools\n\n## Windows\n\n- Windows 10 or later\n\nThere have a bootstrap script to help install the required toolchain and dependencies.\n\nYou can run the script in PowerShell:\n\n```ps\n.\\script\\install-window.ps1\n```\n\n## Linux\n\nRun `./script/bootstrap` to install system dependencies.\n\n## Rust and Cargo\n\nWe use Rust programming language to build the `gpui-component` library. Make sure you have Rust and Cargo installed on your system.\n\n- Rust 1.90 or later\n- Cargo (comes with Rust)\n\nTo install the `gpui-component` library, you can use Cargo, the Rust package manager. Add the following line to your `Cargo.toml` file under the `[dependencies]` section:\n\n```toml-vue\ngpui = \"{{ GPUI_VERSION }}\"\ngpui-component = \"{{ VERSION }}\"\n```\n"
  },
  {
    "path": "docs/docs/root.md",
    "content": "---\norder: -7\n---\n\n# Root View\n\nThe [Root] component for as the root provider of GPUI Component features in a window. We must to use [Root] as the **first level child** of a window to enable GPUI Component features.\n\nThis is important, if we don't use [Root] as the first level child of a window, there will have some unexpected behaviors.\n\n```rs\nfn main() {\n    let app = Application::new();\n\n    app.run(move |cx| {\n        // This must be called before using any GPUI Component features.\n        gpui_component::init(cx);\n\n        cx.spawn(async move |cx| {\n            cx.open_window(WindowOptions::default(), |window, cx| {\n                let view = cx.new(|_| Example);\n                // This first level on the window, should be a Root.\n                cx.new(|cx| Root::new(view, window, cx))\n            })\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n```\n\n## Overlays\n\nWe have dialogs, sheets, notifications, we need placement for them to show, so [Root] provides methods to render these overlays:\n\n- [Root::render_dialog_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_dialog_layer) - Render the current opened modals.\n- [Root::render_sheet_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_sheet_layer) - Render the current opened drawers.\n- [Root::render_notification_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_notification_layer) - Render the notification list.\n\nWe can put these layers in the `render` method your first level view (Root > YourFirstView):\n\n```rs\nstruct MyApp;\n\nimpl Render for MyApp {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .size_full()\n            .child(\"My App Content\")\n            .children(Root::render_dialog_layer(cx))\n            .children(Root::render_sheet_layer(cx))\n            .children(Root::render_notification_layer(cx))\n    }\n}\n```\n\n:::tip\nHere the example we used `children` method, it because if there is no opened dialogs, sheets, notifications, these methods will return `None`, so GPUI will not render anything.\n:::\n\n[Root]: https://docs.rs/gpui-component/latest/gpui_component/root/struct.Root.html\n"
  },
  {
    "path": "docs/docs/theme.md",
    "content": "---\norder: -4\n---\n\n# Theme\n\nAll components support theming through the built-in Theme system, the [ActiveTheme] trait provides access to the current theme colors:\n\n```rs\nuse gpui_component::{ActiveTheme as _};\n\n// Access theme colors in your components\ncx.theme().primary\ncx.theme().background\ncx.theme().foreground\n```\n\nSo if you want use the colors from the current theme, you should keep your component or view have [App] context.\n\n## Theme Registry\n\nThere have more than 20 built-in themes available in [themes](https://github.com/longbridge/gpui-component/tree/main/themes) folder.\n\nhttps://github.com/longbridge/gpui-component/tree/main/themes\n\nAnd we have a [ThemeRegistry] to help us to load themes.\n\n```rs\nuse std::path::PathBuf;\nuse gpui::{App, SharedString};\nuse gpui_component::{Theme, ThemeRegistry};\n\npub fn init(cx: &mut App) {\n    let theme_name = SharedString::from(\"Ayu Light\");\n    // Load and watch themes from ./themes directory\n    if let Err(err) = ThemeRegistry::watch_dir(PathBuf::from(\"./themes\"), cx, move |cx| {\n        if let Some(theme) = ThemeRegistry::global(cx)\n            .themes()\n            .get(&theme_name)\n            .cloned()\n        {\n            Theme::global_mut(cx).apply_config(&theme);\n        }\n    }) {\n        tracing::error!(\"Failed to watch themes directory: {}\", err);\n    }\n}\n```\n\n[ActiveTheme]: https://docs.rs/gpui-component/latest/gpui_component/theme/trait.ActiveTheme.html\n[ThemeRegistry]: https://docs.rs/gpui-component/latest/gpui_component/theme/struct.ThemeRegistry.html\n[App]: https://docs.rs/gpui/latest/gpui/struct.App.html\n"
  },
  {
    "path": "docs/index.md",
    "content": "---\nlayout: home\n---\n\n<script setup>\nimport Index from './index.vue'\n</script>\n\n<Index />\n\n## Simple and Intuitive API\n\nGet started with just a few lines of code. Stateless components\nmake it easy to build complex UIs.\n\n```rs\nButton::new(\"ok\")\n    .primary()\n    .label(\"Click Me\")\n    .on_click(|_, _, _| println!(\"Button clicked!\"))\n```\n\n## Install GPUI Component\n\nAdd the following to your `Cargo.toml`:\n\nGPUI and GPUI Component are under active development, recently GPUI have some new features not published on crates.io, so we recommend using the git version for now.\n\nThe documentation on this site are based on the **Git main branch**, if you use the crates.io version, there may be some differences.\n\n```toml-vue\ngpui = { git = \"https://github.com/zed-industries/zed\" }\ngpui-component = { git = \"https://github.com/longbridge/gpui-component\" }\n```\n\nIf you prefer to use the versions on crates.io. Please visit [docs.rs](https://docs.rs/gpui-component/latest/gpui_component/) to check the API differences.\n\n```toml-vue\ngpui = \"{{ GPUI_VERSION }}\"\ngpui-component = \"{{ VERSION }}\"\n```\n\n## Hello World\n\nThe following `src/main.rs` is a simple \"Hello, World!\" application:\n\n```rs\nuse gpui::*;\nuse gpui_component::{button::*, *};\n\npub struct HelloWorld;\nimpl Render for HelloWorld {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .v_flex()\n            .gap_2()\n            .size_full()\n            .items_center()\n            .justify_center()\n            .child(\"Hello, World!\")\n            .child(\n                Button::new(\"ok\")\n                    .primary()\n                    .label(\"Let's Go!\")\n                    .on_click(|_, _, _| println!(\"Clicked!\")),\n            )\n    }\n}\n\nfn main() {\n    let app = Application::new();\n\n    app.run(move |cx| {\n        // This must be called before using any GPUI Component features.\n        gpui_component::init(cx);\n\n        cx.spawn(async move |cx| {\n            cx.open_window(WindowOptions::default(), |window, cx| {\n                let view = cx.new(|_| HelloWorld);\n                // This first level on the window, should be a Root.\n                cx.new(|cx| Root::new(view, window, cx))\n            })\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n```\n\nRun the program with the following command:\n\n```sh\n$ cargo run\n```\n"
  },
  {
    "path": "docs/index.vue",
    "content": "<template>\n    <div class=\"banner\">\n        <h1>GPUI Component</h1>\n        <div class=\"banner-description\">\n            Rust GUI components for building fantastic cross-platform desktop\n            application by using\n            <a href=\"https://gpui.rs\" target=\"_blank\">GPUI</a>.\n        </div>\n        <div class=\"actions\">\n            <a href=\"docs/getting-started\" class=\"btn-primary\">Get Started</a>\n            <a href=\"docs/components\"><Blocks /> Components</a>\n        </div>\n        <div class=\"version\">\n            Version:\n            <a href=\"https://crates.io/crates/gpui-component\" target=\"_blank\">{{\n                VERSION\n            }}</a>\n        </div>\n    </div>\n    <div class=\"features\">\n        <div class=\"feature-card\">\n            <h3>\n                <div class=\"icon bg-green-500 dark:bg-green-700\">\n                    <Blocks />\n                </div>\n                <div>60+ Components</div>\n            </h3>\n            <div>\n                Comprehensive library of cross-platform desktop UI components\n                for building feature-rich applications.\n            </div>\n        </div>\n        <div class=\"feature-card\">\n            <h3>\n                <div class=\"icon bg-blue-500 dark:bg-blue-700\">\n                    <Zap />\n                </div>\n                <div>High Performance</div>\n            </h3>\n            <div>\n                Virtualized Table and List components for smooth rendering of\n                large datasets with minimal memory footprint.\n            </div>\n        </div>\n\n        <div class=\"feature-card\">\n            <h3>\n                <div class=\"icon bg-red-500 dark:bg-red-700\">\n                    <Palette />\n                </div>\n                <div>Themeable</div>\n            </h3>\n            <div>\n                Built-in theme system with with 20+ themes, and dark mode out of\n                the box.\n            </div>\n        </div>\n\n        <div class=\"feature-card\">\n            <h3>\n                <div class=\"icon bg-yellow-500 dark:bg-yellow-700\">\n                    <Layout />\n                </div>\n                <div>Flexible Layouts</div>\n            </h3>\n            <div>\n                Dock layout for panel arrangements, resizable panels, and\n                freeform layouts for any application structure.\n            </div>\n        </div>\n\n        <div class=\"feature-card\">\n            <h3>\n                <div class=\"icon bg-pink-500 dark:bg-pink-700\">\n                    <BarChart3 />\n                </div>\n                <div>Data Visualization</div>\n            </h3>\n            <div>\n                Built-in chart components for visualizing data with Line, Bar,\n                Area, and Pie charts.\n            </div>\n        </div>\n\n        <div class=\"feature-card\">\n            <h3>\n                <div class=\"icon bg-cyan-500 dark:bg-cyan-700\">\n                    <SquareCode />\n                </div>\n                <div>Code Editor</div>\n            </h3>\n            <div>\n                High-performance code editor with LSP support, syntax\n                highlighting, powered by Tree-sitter and Rope.\n            </div>\n        </div>\n    </div>\n</template>\n\n<script setup>\nimport {\n    Blocks,\n    Zap,\n    Palette,\n    Layout,\n    BarChart3,\n    SquareCode,\n    Github,\n} from \"lucide-vue-next\";\n</script>\n\n<style lang=\"scss\">\n@reference \"./.vitepress/theme/style.css\";\n\n.banner {\n    @apply flex flex-col gap-2 lg:gap-4 -mt-20  py-12 xl:py-30 text-center border border-b-0 border-(--border);\n\n    background: url(\"/home.svg\") no-repeat;\n    background-position: bottom -90px right -90px;\n\n    h1 {\n        @apply mt-20 text-3xl xl:text-5xl font-bold mb-2 text-(--primary);\n    }\n    .banner-description {\n        @apply text-lg xl:text-2xl text-(--muted-foreground);\n    }\n    .actions {\n        @apply gap-4 flex justify-center text-sm;\n        a {\n            @apply flex items-center h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5 no-underline\n            bg-(--secondary) hover:bg-(--secondary)/70 text-(--secondary-foreground);\n\n            &.btn-primary {\n                @apply bg-(--primary) hover:bg-(--primary)/90 text-(--primary-foreground);\n            }\n\n            .lucide {\n                @apply w-4 h-4;\n            }\n        }\n    }\n    .version {\n        @apply text-sm text-(--muted-foreground) pb-10;\n        a {\n            @apply text-(--muted-foreground) no-underline hover:underline;\n        }\n    }\n}\n\n.features {\n    @apply grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 mb-12 border-b border-r  border-(--border);\n}\n\n.feature-card {\n    @apply flex flex-col text-sm gap-2 py-3.5 px-5 border border-b-0 border-(--border);\n    @apply border-r-0 last:border-r-0 md:last:border-b-0 lg:last:border-b-0;\n\n    h3 {\n        @apply m-0 p-0 text-lg text-(--primary) flex gap-3 items-center;\n\n        .icon {\n            @apply flex h-9 w-9 items-center justify-center rounded-md text-white;\n\n            .lucide {\n                @apply w-5 h-5;\n            }\n        }\n    }\n}\n</style>\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"gpui-component-docs\",\n  \"dependencies\": {\n    \"@tailwindcss/postcss\": \"^4.1.15\",\n    \"@tailwindcss/vite\": \"^4.1.15\",\n    \"lucide-react\": \"^0.546.0\",\n    \"lucide-vue-next\": \"^0.546.0\",\n    \"markdown-it-mathjax3\": \"^4\",\n    \"postcss\": \"^8.5.6\",\n    \"tailwindcss\": \"^4.1.15\",\n    \"vite-plugin-toml\": \"^0.8.5\"\n  },\n  \"devDependencies\": {\n    \"@types/bun\": \"^1.3.2\",\n    \"sass\": \"^1\",\n    \"vitepress\": \"^2.0.0-alpha.12\",\n    \"vitepress-plugin-llms\": \"^1.8.0\",\n    \"vitepress-sidebar\": \"^1.18.0\"\n  },\n  \"scripts\": {\n    \"dev\": \"vitepress dev\",\n    \"build\": \"vitepress build\",\n    \"preview\": \"vitepress preview\"\n  },\n  \"private\": true,\n  \"peerDependencies\": {\n    \"typescript\": \"^5\"\n  }\n}\n"
  },
  {
    "path": "docs/postcss.config.mjs",
    "content": "export default {\n  plugins: {\n    \"@tailwindcss/postcss\": {},\n  },\n};\n"
  },
  {
    "path": "docs/skills.md",
    "content": "---\ntitle: Skills\nlayout: home\ndescription: GPUI Component Skills - Available skills for working with GPUI Component\n---\n\n<script setup>\nimport Skills from './skills.vue'\n</script>\n\n<Skills />\n"
  },
  {
    "path": "docs/skills.vue",
    "content": "<template>\n    <div class=\"skills-page\">\n        <h1>GPUI Component Skills</h1>\n        <p class=\"description\">\n            Skills available for working with GPUI Component. These skills\n            provide guidance and best practices for building GPUI applications.\n        </p>\n        <div class=\"skills-list\">\n            <div v-for=\"skill in skills\" :key=\"skill.id\" class=\"skill-card\">\n                <a\n                    :href=\"`https://github.com/longbridge/gpui-component/tree/main/.claude/skills/${skill.id}/SKILL.md`\"\n                    target=\"_blank\"\n                    class=\"skill-link\"\n                >\n                    <h3 class=\"skill-name\">\n                        {{ skill.name }}\n                    </h3>\n                    <div class=\"skill-description\">{{ skill.description }}</div>\n                </a>\n            </div>\n        </div>\n    </div>\n</template>\n\n<script setup>\nimport { data } from \"./data/skills.data\";\nimport { ref } from \"vue\";\n\nconst skills = data;\nconst expandedSkills = ref(new Set());\n\nfunction toggleSkill(skillId) {\n    if (expandedSkills.value.has(skillId)) {\n        expandedSkills.value.delete(skillId);\n    } else {\n        expandedSkills.value.add(skillId);\n    }\n}\n\nfunction isExpanded(skillId) {\n    return expandedSkills.value.has(skillId);\n}\n\nfunction getPreview(content) {\n    // Get first paragraph or first 200 characters\n    const lines = content.split(\"\\n\").filter((line) => line.trim());\n    if (lines.length > 0) {\n        const firstLine = lines[0].trim();\n        if (firstLine.length > 200) {\n            return firstLine.substring(0, 200) + \"...\";\n        }\n        return firstLine;\n    }\n    return content.substring(0, 200) + (content.length > 200 ? \"...\" : \"\");\n}\n\nfunction formatMarkdown(content) {\n    // Simple markdown to HTML conversion for basic formatting\n    let html = content;\n\n    // Code blocks (must come before inline code)\n    html = html.replace(/```(\\w+)?\\n([\\s\\S]*?)```/gim, (match, lang, code) => {\n        const language = lang ? ` class=\"language-${lang}\"` : \"\";\n        return `<pre><code${language}>${escapeHtml(code.trim())}</code></pre>`;\n    });\n\n    // Inline code\n    html = html.replace(/`([^`\\n]+)`/gim, \"<code>$1</code>\");\n\n    // Headers\n    html = html.replace(/^#### (.*$)/gim, \"<h4>$1</h4>\");\n    html = html.replace(/^### (.*$)/gim, \"<h3>$1</h3>\");\n    html = html.replace(/^## (.*$)/gim, \"<h2>$1</h2>\");\n    html = html.replace(/^# (.*$)/gim, \"<h1>$1</h1>\");\n\n    // Bold and italic\n    html = html.replace(/\\*\\*(.*?)\\*\\*/gim, \"<strong>$1</strong>\");\n    html = html.replace(/\\*(.*?)\\*/gim, \"<em>$1</em>\");\n\n    // Lists\n    html = html.replace(/^\\- (.*$)/gim, \"<li>$1</li>\");\n    html = html.replace(/^(\\d+)\\. (.*$)/gim, \"<li>$2</li>\");\n\n    // Wrap consecutive list items in ul/ol\n    html = html.replace(/(<li>.*<\\/li>\\n?)+/gim, (match) => {\n        return \"<ul>\" + match + \"</ul>\";\n    });\n\n    // Paragraphs (split by double newlines)\n    const paragraphs = html.split(/\\n\\n+/);\n    html = paragraphs\n        .map((p) => {\n            p = p.trim();\n            if (!p || p.startsWith(\"<\")) return p; // Already formatted\n            return \"<p>\" + p + \"</p>\";\n        })\n        .join(\"\\n\\n\");\n\n    return html;\n}\n\nfunction escapeHtml(text) {\n    // Simple HTML escaping for SSR compatibility\n    return text\n        .replace(/&/g, \"&amp;\")\n        .replace(/</g, \"&lt;\")\n        .replace(/>/g, \"&gt;\")\n        .replace(/\"/g, \"&quot;\")\n        .replace(/'/g, \"&#039;\");\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@reference \"./.vitepress/theme/style.css\";\n\n.skills-page {\n    h1 {\n        @apply text-3xl xl:text-4xl font-bold mb-4 text-(--primary);\n    }\n\n    .description {\n        @apply text-lg text-(--muted-foreground) mb-8;\n    }\n}\n\n.skills-list {\n    @apply flex flex-col gap-6;\n}\n\n.skill-card {\n    @apply border border-(--border) rounded-lg py-3 gap-3 px-4 hover:bg-(--secondary) transition-colors;\n\n    a {\n        @apply no-underline flex flex-col gap-3;\n    }\n\n    .skill-name {\n        @apply text-xl font-semibold m-0;\n\n        .skill-link {\n            @apply text-(--primary) hover:text-(--primary)/80 no-underline;\n        }\n    }\n\n    .skill-description {\n        @apply text-sm text-(--muted-foreground);\n    }\n\n    .skill-preview {\n        @apply text-sm text-(--muted-foreground) mb-4 italic;\n    }\n\n    .skill-content {\n        @apply text-sm text-(--foreground) mb-4;\n\n        :deep(h2) {\n            @apply text-lg font-semibold mt-4 mb-2 text-(--primary);\n        }\n\n        :deep(h3) {\n            @apply text-base font-semibold mt-3 mb-2 text-(--primary);\n        }\n\n        :deep(p) {\n            @apply mb-2;\n        }\n\n        :deep(code) {\n            @apply bg-(--secondary) px-1.5 py-0.5 rounded text-sm;\n        }\n\n        :deep(pre) {\n            @apply bg-(--secondary) p-4 rounded-lg overflow-x-auto my-4;\n\n            code {\n                @apply bg-transparent p-0;\n            }\n        }\n\n        :deep(ul),\n        :deep(ol) {\n            @apply ml-6 mb-2;\n        }\n\n        :deep(li) {\n            @apply mb-1;\n        }\n    }\n\n    .skill-actions {\n        @apply flex items-center gap-4 mt-4;\n    }\n\n    .view-detail-link {\n        @apply text-sm text-(--primary) hover:text-(--primary)/80 no-underline font-medium;\n    }\n\n    .toggle-button {\n        @apply text-sm text-(--muted-foreground) hover:text-(--foreground) underline cursor-pointer bg-transparent border-none p-0;\n    }\n}\n</style>\n"
  },
  {
    "path": "docs/src/dark.theme.json",
    "content": "{\n  \"name\": \"macos-classic-dark\",\n  \"author\": \"Jason Lee\",\n  \"doc\": \"Based on macOS classic Dark 2 theme.\",\n  \"maintainers\": [\"Jason Lee <huacnlee@gmail.com>\"],\n  \"type\": \"dark\",\n  \"semanticClass\": \"theme.macos-classic-dark2\",\n  \"colors\": {\n    \"editor.background\": \"#151515\",\n    \"editor.foreground\": \"#CACCCA\",\n    \"editor.lineHighlightBackground\": \"#272727\",\n    \"editorIndentGuide.background\": \"#303030\",\n    \"editorLineNumber.foreground\": \"#8F8F8F\",\n    \"editorLineNumber.background\": \"#2C2C2C\",\n    \"editorBracketMatch.background\": \"#1E1D1E\",\n    \"editorBracketMatch.border\": \"#303030\",\n    \"editorGroupHeader.tabsBackground\": \"#1B1B1B\",\n    \"editorGroupHeader.tabsBorder\": \"#444444\",\n    \"editorGroup.border\": \"#393939\",\n    \"editorGroupHeader.border\": \"#393939\",\n    \"tab.border\": \"#3F3F3F\",\n    \"tab.activeBackground\": \"#101010\",\n    \"tab.activeForeground\": \"#FFFFFF\",\n    \"tab.activeBorder\": \"#101010\",\n    \"tab.activeBorderTop\": \"#101010\",\n    \"tab.inactiveBackground\": \"#242424\",\n    \"tab.inactiveForeground\": \"#9E9E9E\",\n    \"statusBar.foreground\": \"#B2B2B2\",\n    \"statusBar.noFolderForeground\": \"#B2B2B2\",\n    \"statusBar.background\": \"#262626\",\n    \"statusBar.noFolderBackground\": \"#262626\",\n    \"statusBar.border\": \"#4A494A\",\n    \"statusBar.debuggingBackground\": \"#262626\",\n    \"statusBar.debuggingForeground\": \"#B2B2B2\",\n    \"activityBar.background\": \"#262626\",\n    \"activityBar.border\": \"#2A2A2A\",\n    \"activityBar.foreground\": \"#6A6A69\",\n    \"activityBar.inactiveForeground\": \"#5A5A59\",\n    \"activityBarBadge.background\": \"#010101\",\n    \"activityBarBadge.foreground\": \"#E0E0E0\",\n    \"sideBar.background\": \"#1c1c1c\",\n    \"sideBar.foreground\": \"#B2B2B2\",\n    \"sideBar.border\": \"#3A3A3A\",\n    \"sidebarTitle.background\": \"#302F30\",\n    \"sideBarTitle.foreground\": \"#898989\",\n    \"sideBarSectionHeader.background\": \"#302F30\",\n    \"sideBarSectionHeader.foreground\": \"#898989\",\n    \"sideBarSectionHeader.border\": \"#363636\",\n    \"list.hoverBackground\": \"#252426\",\n    \"list.hoverForeground\": \"#B2B2B2\",\n    \"list.activeSelectionBackground\": \"#353436\",\n    \"list.activeSelectionForeground\": \"#E3E4E4\",\n    \"list.inactiveSelectionBackground\": \"#353436\",\n    \"list.inactiveSelectionForeground\": \"#B2B2B2\",\n    \"titleBar.inactiveBackground\": \"#333333\",\n    \"titleBar.activeBackground\": \"#292929\",\n    \"titleBar.border\": \"#3A393A\",\n    \"notification.background\": \"#54a3ff\",\n    \"panel.background\": \"#1E1D1E\",\n    \"panel.border\": \"#3A3A3A\",\n    \"panelInput.border\": \"#303030\",\n    \"panelTitle.activeBorder\": \"#3A3A3A\",\n    \"panelTitle.activeForeground\": \"#B2B2B2\",\n    \"inputOption.activeBackground\": \"#303030\",\n    \"input.background\": \"#1E1D1E\",\n    \"input.foreground\": \"#DDDDDD\",\n    \"input.border\": \"#303030\",\n    \"focusBorder\": \"#3A3A3AFF\",\n    \"dropdown.background\": \"#1E1D1E\",\n    \"dropdown.border\": \"#303030\",\n    \"dropdown.foreground\": \"#B2B2B2\",\n    \"scrollbarSlider.background\": \"#2C2D2D\",\n    \"scrollbarSlider.hoverBackground\": \"#2C2D2D\",\n    \"scrollbarSlider.activeBackground\": \"#2C2D2D\",\n    \"scrollbar.shadow\": \"#1e1f2099\",\n    \"editorCursor.foreground\": \"#CACCCA\",\n    \"selection.background\": \"#004a9e\",\n    \"editor.selectionBackground\": \"#004a9e\",\n    \"editor.selectedForeground\": \"#FFFFFF\",\n    \"editor.findMatchHighlightBackground\": \"#705f00\",\n    \"editor.findMatchBackground\": \"#194dbd\",\n    \"editor.wordHighlightBackground\": \"#444547\",\n    \"terminal.ansiBlue\": \"#282BFF\",\n    \"terminal.ansiBrightBlue\": \"#272BFF\",\n    \"terminal.ansiCyan\": \"#0B0098\",\n    \"terminal.ansiBrightCyan\": \"#301CAE\",\n    \"terminal.ansiGreen\": \"#277F2B\",\n    \"terminal.ansiBrightGreen\": \"#449444\",\n    \"terminal.ansiMagenta\": \"#AE30C2\",\n    \"terminal.ansiBrightMagenta\": \"#BA3DCA\",\n    \"terminal.ansiRed\": \"#C71211\",\n    \"terminal.ansiBrightRed\": \"#D9564E\",\n    \"terminal.ansiWhite\": \"#131313\",\n    \"terminal.ansiBrightWhite\": \"#eeeeee\",\n    \"editorWidget.background\": \"#272727\",\n    \"editorWidget.border\": \"#272727\",\n    \"button.border\": \"#252425\",\n    \"button.background\": \"#0854D0\",\n    \"button.hoverBackground\": \"#1f6ae2\",\n    \"button.secondaryBackground\": \"#3d3d3d\",\n    \"button.secondaryHoverBackground\": \"#4c4c4c\",\n    \"widget.shadow\": \"#191919\",\n    \"widget.border\": \"#3a3a3a\",\n    \"editorSuggestWidget.background\": \"#363636\",\n    \"editorSuggestWidget.selectedBackground\": \"#6a6a6a\",\n    \"editorSuggestWidget.selectedForeground\": \"#ffffff\",\n    \"editorSuggestWidget.border\": \"#3A3A3A\",\n    \"editorSuggestWidget.highlightForeground\": \"#ffff00\",\n    \"editorHoverWidget.background\": \"#363636\",\n    \"editorHoverWidget.selectedBackground\": \"#464646\",\n    \"editorHoverWidget.border\": \"#3A3A3A\",\n    \"problemsWarningIcon.foreground\": \"#f26d0d\"\n  },\n  \"tokenColors\": [\n    {\n      \"settings\": {\n        \"foreground\": \"#CACCCA\",\n        \"background\": \"#131313\"\n      }\n    },\n    {\n      \"scope\": \"emphasis\",\n      \"settings\": {\n        \"fontStyle\": \"italic\"\n      }\n    },\n    {\n      \"scope\": [\"strong\", \"markup.heading.markdown\", \"markup.bold.markdown\"],\n      \"settings\": {\n        \"fontStyle\": \"bold\"\n      }\n    },\n    {\n      \"scope\": [\"markup.italic.markdown\"],\n      \"settings\": {\n        \"fontStyle\": \"italic\"\n      }\n    },\n    {\n      \"scope\": \"meta.link.inline.markdown\",\n      \"settings\": {\n        \"fontStyle\": \"underline\",\n        \"foreground\": \"#4C9BE9\"\n      }\n    },\n    {\n      \"scope\": [\"comment\", \"markup.fenced_code\", \"markup.inline\"],\n      \"settings\": {\n        \"foreground\": \"#9E9E9E\"\n      }\n    },\n    {\n      \"scope\": \"string\",\n      \"settings\": {\n        \"foreground\": \"#76BA53\"\n      }\n    },\n    {\n      \"scope\": [\n        \"variable.other.constant\",\n        \"variable.other.class\",\n        \"meta.property-name\",\n        \"meta.property-value\",\n        \"support\",\n        \"constant.language.boolean\",\n        \"support.function.kernel\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#CACCCA\"\n      }\n    },\n    {\n      \"scope\": [\"constant.language\", \"constant.other.color\"],\n      \"settings\": {\n        \"foreground\": \"#ca8a04\"\n      }\n    },\n    {\n      \"scope\": [\n        \"keyword\",\n        \"storage.modifier\",\n        \"storage.type\",\n        \"variable.language.this\",\n        \"punctuation.definition.template-expression\",\n        \"constant.numeric\",\n        \"entity.other.attribute-name\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#c28b12\"\n      }\n    },\n    {\n      \"scope\": [\"meta.tag.structure\", \"entity.name.tag\"],\n      \"settings\": {\n        \"foreground\": \"#9a9576\"\n      }\n    },\n    {\n      \"scope\": [\n        \"keyword.operator.accessor\",\n        \"meta.group.braces.round.function.arguments\",\n        \"meta.template.expression\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#AE9513\"\n      }\n    },\n    {\n      \"scope\": [\n        \"entity.name.type.class\",\n        \"entity.other.inherited-class\",\n        \"source.css\",\n        \"entity.name.tag.css\",\n        \"entity.other.attribute-name.class.css\",\n        \"punctuation.definition.entity.css\",\n        \"meta.attribute-selector.scss\",\n        \"entity.other.attribute-name.attribute.scss\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#AC98AD\"\n      }\n    },\n    {\n      \"scope\": [\n        \"variable.language.self\",\n        \"variable.other.readwrite.instance\",\n        \"meta.definition.variable.scss\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#E1D797\"\n      }\n    },\n    {\n      \"scope\": [\n        \"entity.name.type\",\n        \"entity.other.inherited-class\",\n        \"variable.other.object.property\",\n        \"meta.instance.constructor\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#b54e05\"\n      }\n    },\n    {\n      \"scope\": [\n        \"support.constant.property-value\",\n        \"support.variable.property.dom\",\n        \"support.type.property-name.json\",\n        \"punctuation.separator.key-value\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#CACCCA\"\n      }\n    },\n    {\n      \"scope\": [\"meta.function-call\", \"variable.parameter.function\"],\n      \"settings\": {\n        \"foreground\": \"#CACCCA\"\n      }\n    },\n    {\n      \"scope\": [\"support.function\", \"entity.name.function\"],\n      \"settings\": {\n        \"foreground\": \"#fdd888\"\n      }\n    },\n    {\n      \"scope\": [\"variable.other.constant\", \"variable.language.this\"],\n      \"settings\": {\n        \"foreground\": \"#7D7A68\"\n      }\n    },\n    {\n      \"scope\": [\"constant.other.symbol\"],\n      \"settings\": {\n        \"foreground\": \"#7D7A68\"\n      }\n    },\n    {\n      \"scope\": [\n        \"string.quoted\",\n        \"string.regexp\",\n        \"string.interpolated\",\n        \"string.template\",\n        \"keyword.other.template\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#62BA46\"\n      }\n    },\n    {\n      \"scope\": \"token.info-token\",\n      \"settings\": {\n        \"foreground\": \"#316bcd\"\n      }\n    },\n    {\n      \"scope\": \"token.warn-token\",\n      \"settings\": {\n        \"foreground\": \"#cd9731\"\n      }\n    },\n    {\n      \"scope\": \"token.error-token\",\n      \"settings\": {\n        \"foreground\": \"#cd3131\"\n      }\n    },\n    {\n      \"scope\": \"token.debug-token\",\n      \"settings\": {\n        \"foreground\": \"#800080\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "docs/src/light.theme.json",
    "content": "{\n  \"name\": \"macos-classic-light\",\n  \"author\": \"Jason Lee\",\n  \"doc\": \"Based on macOS classic theme.\",\n  \"maintainers\": [\"Jason Lee <huacnlee@gmail.com>\"],\n  \"type\": \"light\",\n  \"semanticClass\": \"theme.macos-classic\",\n  \"colors\": {\n    \"editor.background\": \"#f6f6f7\",\n    \"editor.foreground\": \"#000000\",\n    \"editor.lineHighlightBackground\": \"#F5F5F5\",\n    \"editorIndentGuide.activeBackground\": \"#D2D2D2\",\n    \"editorIndentGuide.background\": \"#E3E4E4\",\n    \"editorLineNumber.foreground\": \"#929292\",\n    \"editorLineNumber.background\": \"#e4e4e4\",\n    \"editorBracketMatch.background\": \"#f1f8ff\",\n    \"editorBracketMatch.border\": \"#c8e1ff\",\n    \"editorGroupHeader.tabsBackground\": \"#EAEAEA\",\n    \"editorGroupHeader.tabsBorder\": \"#D2D2D2\",\n    \"editorGroup.border\": \"#a09ea0\",\n    \"editorGroupHeader.border\": \"#C3C3C3\",\n    \"tab.activeBackground\": \"#F9F9F9\",\n    \"tab.activeForeground\": \"#474747\",\n    \"tab.activeBorder\": \"#DADADA\",\n    \"tab.activeBorderTop\": \"#FFFFFF\",\n    \"tab.inactiveBackground\": \"#ECECEC\",\n    \"tab.inactiveForeground\": \"#6D6D6D\",\n    \"tab.border\": \"#D2D2D2\",\n    \"statusBar.background\": \"#F5F6F6\",\n    \"statusBar.foreground\": \"#494949\",\n    \"statusBar.noFolderForeground\": \"#575557\",\n    \"statusBar.noFolderBackground\": \"#ededed\",\n    \"statusBar.border\": \"#DCDBDA\",\n    \"statusBar.debuggingBackground\": \"#fafbfc\",\n    \"statusBar.debuggingForeground\": \"#24292e\",\n    \"activityBar.background\": \"#ECECEC\",\n    \"activityBar.border\": \"#D8D8D8\",\n    \"activityBar.foreground\": \"#6A6A69\",\n    \"activityBar.inactiveForeground\": \"#6A6A69\",\n    \"activityBarBadge.background\": \"#FFFFFF\",\n    \"activityBarBadge.foreground\": \"#575557\",\n    \"sideBar.background\": \"#EAEAEA\",\n    \"sideBar.foreground\": \"#464646\",\n    \"sideBar.border\": \"#D2D2D2\",\n    \"sidebarTitle.background\": \"#f6f6f6\",\n    \"sideBarTitle.foreground\": \"#575557\",\n    \"sideBarSectionHeader.background\": \"#f6f6f6\",\n    \"sideBarSectionHeader.foreground\": \"#808080\",\n    \"sideBarSectionHeader.border\": \"#d5d5d5\",\n    \"list.hoverBackground\": \"#D0D0D0\",\n    \"list.hoverForeground\": \"#262426\",\n    \"list.activeSelectionBackground\": \"#0069d9\",\n    \"list.activeSelectionForeground\": \"#ffffff\",\n    \"list.inactiveSelectionBackground\": \"#0069d9\",\n    \"list.inactiveSelectionForeground\": \"#ffffff\",\n    \"list.focusHighlightForeground\": \"#9dddff\",\n    \"titlebar.inactiveForeground\": \"#E0E0E0\",\n    \"titleBar.activeBackground\": \"#FDFDFD\",\n    \"titleBar.border\": \"#D2D2D2\",\n    \"panel.background\": \"#F9F9F9\",\n    \"panel.border\": \"#DCDBDA\",\n    \"panelInput.border\": \"#DCDBDA\",\n    \"panelTitle.activeBorder\": \"#101010\",\n    \"panelTitle.activeForeground\": \"#24292e\",\n    \"input.background\": \"#ffffff\",\n    \"input.foreground\": \"#262426\",\n    \"input.border\": \"#e0e0e0\",\n    \"inputOption.activeBackground\": \"#e0e0e0\",\n    \"focusBorder\": \"#eeeeee\",\n    \"dropdown.background\": \"#ffffff\",\n    \"dropdown.border\": \"#c4c4c4\",\n    \"dropdown.foreground\": \"#262426\",\n    \"scrollbarSlider.background\": \"#CACACA\",\n    \"scrollbarSlider.hoverBackground\": \"#C0C0C0\",\n    \"scrollbarSlider.activeBackground\": \"#C0C0C0\",\n    \"scrollbar.shadow\": \"#f0f0f0\",\n    \"editorCursor.foreground\": \"#101010\",\n    \"editor.wordHighlightBackground\": \"#DFEDFF\",\n    \"terminal.ansiBlue\": \"#282BFF\",\n    \"terminal.ansiBrightBlue\": \"#272BFF\",\n    \"terminal.ansiCyan\": \"#0B0098\",\n    \"terminal.ansiBrightCyan\": \"#301CAE\",\n    \"terminal.ansiGreen\": \"#277F2B\",\n    \"terminal.ansiBrightGreen\": \"#449444\",\n    \"terminal.ansiMagenta\": \"#AE30C2\",\n    \"terminal.ansiBrightMagenta\": \"#BA3DCA\",\n    \"terminal.ansiRed\": \"#C71211\",\n    \"terminal.ansiBrightRed\": \"#D9564E\",\n    \"terminal.ansiWhite\": \"#ffffff\",\n    \"terminal.ansiBrightWhite\": \"#eeeeee\",\n    \"editorWidget.background\": \"#EAEAEA\",\n    \"editorWidget.border\": \"#eaeaea\",\n    \"button.secondaryBackground\": \"#505050\",\n    \"button.secondaryHoverBackground\": \"#505050\",\n    \"button.border\": \"#EAEAEA\",\n    \"button.background\": \"#1f6ae2\",\n    \"button.hoverBackground\": \"#1f6ae2\",\n    \"widget.shadow\": \"#cecece\",\n    \"widget.border\": \"#cfcfcf\",\n    \"notificationCenterHeader.background\": \"#f7f7f7\",\n    \"notifications.border\": \"#e5e5e6\",\n    \"notification.background\": \"#54a3ff\",\n    \"editorSuggestWidget.background\": \"#FBFBFB\",\n    \"editorSuggestWidget.selectedBackground\": \"#0069D9\",\n    \"editorSuggestWidget.selectedForeground\": \"#FFFFFF\",\n    \"editorSuggestWidget.highlightForeground\": \"#0069D9\",\n    \"editorSuggestWidget.border\": \"#E0E0E0\",\n    \"editorHoverWidget.background\": \"#FBFBFB\",\n    \"editorHoverWidget.selectedBackground\": \"#0069D9\",\n    \"editorHoverWidget.border\": \"#E0E0E0\",\n    \"problemsWarningIcon.foreground\": \"#f26d0d\"\n  },\n  \"tokenColors\": [\n    {\n      \"settings\": {\n        \"foreground\": \"#000000\",\n        \"background\": \"#f6f6f7\"\n      }\n    },\n    {\n      \"scope\": \"emphasis\",\n      \"settings\": {\n        \"fontStyle\": \"italic\"\n      }\n    },\n    {\n      \"scope\": [\"strong\", \"markup.heading.markdown\", \"markup.bold.markdown\"],\n      \"settings\": {\n        \"fontStyle\": \"bold\"\n      }\n    },\n    {\n      \"scope\": [\"markup.italic.markdown\"],\n      \"settings\": {\n        \"fontStyle\": \"italic\"\n      }\n    },\n    {\n      \"scope\": \"meta.link.inline.markdown\",\n      \"settings\": {\n        \"fontStyle\": \"underline\",\n        \"foreground\": \"#005cc5\"\n      }\n    },\n    {\n      \"scope\": [\"comment\", \"markup.fenced_code\", \"markup.inline\"],\n      \"settings\": {\n        \"foreground\": \"#9a9a9a\"\n      }\n    },\n    {\n      \"scope\": \"string\",\n      \"settings\": {\n        \"foreground\": \"#036A07\"\n      }\n    },\n    {\n      \"scope\": [\n        \"variable.other.constant\",\n        \"variable.other.class\",\n        \"meta.property-name\",\n        \"meta.property-value\",\n        \"support\",\n        \"constant.language.boolean\",\n        \"support.function.kernel\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#8090e5\"\n      }\n    },\n    {\n      \"scope\": [\"constant.language\", \"constant.other.color\"],\n      \"settings\": {\n        \"foreground\": \"#C5060B\"\n      }\n    },\n    {\n      \"scope\": [\n        \"keyword\",\n        \"storage.modifier\",\n        \"storage.type\",\n        \"support.function\",\n        \"variable.language.this\",\n        \"punctuation.definition.template-expression\",\n        \"constant.numeric\",\n        \"entity.name.tag\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#0433ff\"\n      }\n    },\n    {\n      \"scope\": [\"entity.other.attribute-name\", \"meta.tag.structure\"],\n      \"settings\": {\n        \"foreground\": \"#0000A2\"\n      }\n    },\n    {\n      \"scope\": [\n        \"keyword.operator.accessor\",\n        \"meta.group.braces.round.function.arguments\",\n        \"meta.template.expression\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#000000\"\n      }\n    },\n    {\n      \"scope\": [\n        \"entity.name.type.class\",\n        \"entity.other.inherited-class\",\n        \"meta.property-value\",\n        \"source.css\",\n        \"entity.name.tag.css\",\n        \"entity.other.attribute-name.class.css\",\n        \"punctuation.definition.entity.css\",\n        \"meta.attribute-selector.scss\",\n        \"entity.other.attribute-name.attribute.scss\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#000000\"\n      }\n    },\n    {\n      \"scope\": [\n        \"variable.language.self\",\n        \"variable.other.readwrite.instance\",\n        \"meta.definition.variable.scss\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#318495\"\n      }\n    },\n    {\n      \"scope\": [\n        \"entity.name.type\",\n        \"entity.other.inherited-class\",\n        \"variable.other.object.property\",\n        \"meta.instance.constructor\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#571ab7\"\n      }\n    },\n    {\n      \"scope\": [\"support.constant.property-value\"],\n      \"settings\": {\n        \"foreground\": \"#119605\"\n      }\n    },\n    {\n      \"scope\": [\n        \"meta.function-call\",\n        \"variable.parameter.function\",\n        \"support.variable.property.dom\",\n        \"support.type.property-name.json\",\n        \"punctuation.separator.key-value\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#333333\"\n      }\n    },\n    {\n      \"scope\": [\"entity.name.function\"],\n      \"settings\": {\n        \"foreground\": \"#0000A2\"\n      }\n    },\n    {\n      \"scope\": [\"variable.other.constant\", \"variable.language.this\"],\n      \"settings\": {\n        \"foreground\": \"#3b96a6\"\n      }\n    },\n    {\n      \"scope\": [\"constant.other.symbol\"],\n      \"settings\": {\n        \"foreground\": \"#d21f07\"\n      }\n    },\n    {\n      \"scope\": [\n        \"string.quoted\",\n        \"string.regexp\",\n        \"string.interpolated\",\n        \"string.template\",\n        \"keyword.other.template\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#036A07\"\n      }\n    },\n    {\n      \"scope\": \"token.info-token\",\n      \"settings\": {\n        \"foreground\": \"#316bcd\"\n      }\n    },\n    {\n      \"scope\": \"token.warn-token\",\n      \"settings\": {\n        \"foreground\": \"#cd9731\"\n      }\n    },\n    {\n      \"scope\": \"token.error-token\",\n      \"settings\": {\n        \"foreground\": \"#cd3131\"\n      }\n    },\n    {\n      \"scope\": \"token.debug-token\",\n      \"settings\": {\n        \"foreground\": \"#800080\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "examples/README.md",
    "content": "# GPUI Component basic examples\n\nThis folder contains basic examples of how to use the GPUI Component library. Each example demonstrates a specific feature or functionality of the library.\n\nUnlike the examples in the `story` folder, these examples focus on 1 example for 1 feature, making it easier to understand and implement specific functionalities in your own projects.\n\n## Contributing\n\nFeel free to contribute more examples to this folder!\n\nIf you have a specific use case or feature you'd like to demonstrate, please create a new example file and submit a pull request. We will happy to merge it into the repository.\n\nWhen creating a new example, please follow these guidelines:\n\n1. Keep 1 example just doing 1 thing for more clarity.\n2. Testing the example to ensure it works as expected.\n3. Write some comment at some key parts of the code to explain what it does.\n4. Following the code style and name style used in the existing examples or in entire of GPUI Component.\n"
  },
  {
    "path": "examples/app_assets/Cargo.toml",
    "content": "[package]\nname = \"app_assets\"\ndescription = \"Example to load icons or images from assets folder.\"\nversion = \"0.5.1\"\npublish = false\nedition.workspace = true\n\n[dependencies]\nanyhow.workspace = true\ngpui.workspace = true\ngpui_platform.workspace = true\ngpui-component = { workspace = true }\nrust-embed = { version = \"8\", features = [\"interpolate-folder-path\"] }\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "examples/app_assets/README.md",
    "content": "## Icon assets in GPUI Component\n\nThe [IconName](https://github.com/longbridge/gpui-component/blob/6998708b817024c2ac0f1ea164d74ddfc024e124/crates/ui/src/icon.rs#L9) is a enum that defined a bunch of icon names, because some internal components in GPUI Component will use them.\n\nYou can see, we have a lot of svg icon files in the `assets/icons` folder, but we are not embed all of the icon files in the library by default. This for keep the library size small.\n\nSo you must have your own icon files to use the `Icon` component in GPUI Component.\n\nYou can download the icon files from [here](https://lucide.dev/) or use your own icon files as you wish, just use the same filename as the icon name (match with the `IconName` defined) you want to use.\n\nFor example your assets folder:\n\n```\napp_root\n  assets\n    icons\n      close.svg\n      menu.svg\n      ...\n  src\n    main.rs\n  Cargo.toml\n```\n\nYou also can just copy the svg files you want from the `assets/icons` folder in GPUI Component repo to your own assets folder.\n\n## How to use\n\nYou need define a `Assets` struct with rust-embed to register assets to GPUI application.\n\n```rs\nuse anyhow::anyhow;\nuse gpui::*;\nuse rust_embed::RustEmbed;\nuse std::borrow::Cow;\n\n#[derive(RustEmbed)]\n#[folder = \"./assets\"]\n#[include = \"icons/**/*.svg\"]\npub struct Assets;\n\nimpl AssetSource for Assets {\n    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {\n        Self::get(path)\n            .map(|f| Some(f.data))\n            .ok_or_else(|| anyhow!(\"could not find asset at path \\\"{path}\\\"\"))\n    }\n\n    fn list(&self, path: &str) -> Result<Vec<SharedString>> {\n        Ok(Self::iter()\n            .filter_map(|p| p.starts_with(path).then(|| p.into()))\n            .collect())\n    }\n}\n\nfn main() {\n    // Call with_assets to register assets\n    let app = gpui_platform::application().with_assets(Assets);\n\n    // ...\n}\n```\n\n## Use default bundled assets.\n\nThe `gpui-component-assets` crate provide a default bundled assets implementation that include all the icon files in the `assets/icons` folder.\n\nIf you don't want to manage your own icon files, you can just use the default bundled assets.\n\nJust add `gpui-component-assets` as a dependency in your `Cargo.toml`:\n\n```toml\n[dependencies]\ngpui-component = \"*\"\ngpui-component-assets = \"*\"\n```\n\nAnd then use it in your application:\n\n```rs\nlet app = gpui_platform::application().with_assets(gpui_component_assets::Assets);\n```\n"
  },
  {
    "path": "examples/app_assets/src/main.rs",
    "content": "use anyhow::anyhow;\nuse gpui::*;\nuse gpui_component::{IconName, Root, v_flex};\nuse rust_embed::RustEmbed;\nuse std::borrow::Cow;\n\n/// An asset source that loads assets from the `./assets` folder.\n#[derive(RustEmbed)]\n#[folder = \"./assets\"]\n#[include = \"icons/**/*.svg\"]\npub struct Assets;\n\nimpl AssetSource for Assets {\n    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {\n        if path.is_empty() {\n            return Ok(None);\n        }\n\n        Self::get(path)\n            .map(|f| Some(f.data))\n            .ok_or_else(|| anyhow!(\"could not find asset at path \\\"{path}\\\"\"))\n    }\n\n    fn list(&self, path: &str) -> Result<Vec<SharedString>> {\n        Ok(Self::iter()\n            .filter_map(|p| p.starts_with(path).then(|| p.into()))\n            .collect())\n    }\n}\n\npub struct Example;\nimpl Render for Example {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .gap_2()\n            .size_full()\n            .items_center()\n            .justify_center()\n            .text_center()\n            .child(IconName::Inbox)\n            .child(IconName::Bot)\n    }\n}\n\nfn main() {\n    // Register Assets to GPUI application.\n    let app = gpui_platform::application().with_assets(Assets);\n\n    app.run(move |cx| {\n        // We must initialize gpui_component before using it.\n        gpui_component::init(cx);\n\n        cx.spawn(async move |cx| {\n            cx.open_window(WindowOptions::default(), |window, cx| {\n                let view = cx.new(|_| Example);\n                // The first level on the window must be Root.\n                cx.new(|cx| Root::new(view, window, cx))\n            })\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n"
  },
  {
    "path": "examples/color_mix_oklab.rs",
    "content": "use gpui::*;\nuse gpui_component::{\n    h_flex,\n    theme::{ActiveTheme, Colorize},\n    v_flex, Root, Sizable,\n};\n\nactions!(demo, [Quit]);\n\nstruct ColorMixDemo {\n    focus_handle: FocusHandle,\n}\n\nimpl ColorMixDemo {\n    fn new(cx: &mut WindowContext) -> Self {\n        Self {\n            focus_handle: cx.focus_handle(),\n        }\n    }\n}\n\nimpl Render for ColorMixDemo {\n    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {\n        let destructive = cx.theme().destructive;\n        let transparent = hsla(0.0, 0.0, 0.0, 0.0);\n\n        // 类似 CSS: color-mix(in oklab, var(--destructive) 20%, transparent)\n        let mixed_20 = destructive.mix_oklab(transparent, 0.2);\n        let mixed_50 = destructive.mix_oklab(transparent, 0.5);\n        let mixed_80 = destructive.mix_oklab(transparent, 0.8);\n\n        v_flex()\n            .gap_4()\n            .p_4()\n            .track_focus(&self.focus_handle)\n            .child(\n                v_flex()\n                    .gap_2()\n                    .child(\"Oklab mix with transparent (使用 premultiplied alpha):\")\n                    .child(\n                        h_flex()\n                            .gap_2()\n                            .child(\n                                div()\n                                    .size_20()\n                                    .bg(destructive)\n                                    .child(\"100%\")\n                                    .text_color(gpui::white()),\n                            )\n                            .child(\n                                div()\n                                    .size_20()\n                                    .bg(mixed_80)\n                                    .child(\"80%\")\n                                    .text_color(gpui::white()),\n                            )\n                            .child(\n                                div()\n                                    .size_20()\n                                    .bg(mixed_50)\n                                    .child(\"50%\")\n                                    .text_color(gpui::white()),\n                            )\n                            .child(\n                                div()\n                                    .size_20()\n                                    .bg(mixed_20)\n                                    .child(\"20%\")\n                                    .text_color(gpui::white()),\n                            ),\n                    ),\n            )\n            .child(\n                v_flex()\n                    .gap_2()\n                    .child(format!(\n                        \"原始颜色: {} (alpha: {:.2})\",\n                        destructive.to_hex(),\n                        destructive.a\n                    ))\n                    .child(format!(\n                        \"80% 混合: {} (alpha: {:.2})\",\n                        mixed_80.to_hex(),\n                        mixed_80.a\n                    ))\n                    .child(format!(\n                        \"50% 混合: {} (alpha: {:.2})\",\n                        mixed_50.to_hex(),\n                        mixed_50.a\n                    ))\n                    .child(format!(\n                        \"20% 混合: {} (alpha: {:.2})\",\n                        mixed_20.to_hex(),\n                        mixed_20.a\n                    )),\n            )\n            .child(\n                v_flex()\n                    .mt_4()\n                    .gap_2()\n                    .child(\"比较 HSL 和 Oklab 混合的差异 (50% transparent):\")\n                    .child(\n                        h_flex()\n                            .gap_4()\n                            .child(\n                                v_flex()\n                                    .gap_1()\n                                    .child(\"HSL 混合:\")\n                                    .child(\n                                        div()\n                                            .size_16()\n                                            .bg(destructive.mix(transparent, 0.5))\n                                            .text_color(gpui::white())\n                                            .child(\"HSL\"),\n                                    )\n                                    .child(format!(\n                                        \"{}\",\n                                        destructive.mix(transparent, 0.5).to_hex()\n                                    )),\n                            )\n                            .child(\n                                v_flex()\n                                    .gap_1()\n                                    .child(\"Oklab 混合 (正确):\")\n                                    .child(\n                                        div()\n                                            .size_16()\n                                            .bg(destructive.mix_oklab(transparent, 0.5))\n                                            .text_color(gpui::white())\n                                            .child(\"Oklab\"),\n                                    )\n                                    .child(format!(\"{}\", mixed_50.to_hex())),\n                            ),\n                    ),\n            )\n            .child(\n                v_flex()\n                    .mt_4()\n                    .gap_2()\n                    .child(\"说明:\")\n                    .child(\"- Oklab 混合保持了原始颜色的色调，只改变透明度\")\n                    .child(\"- HSL 混合会使颜色变暗（因为与黑色透明混合）\")\n                    .child(\"- Oklab 使用 premultiplied alpha 算法，符合 CSS color-mix 规范\"),\n            )\n    }\n}\n\nfn main() {\n    env_logger::init();\n\n    Application::new().run(move |cx| {\n        gpui_component::init(cx);\n\n        cx.activate(true);\n        cx.on_action(|_: &Quit, cx: &mut AppContext| {\n            cx.quit();\n        });\n        cx.bind_keys([KeyBinding::new(\"cmd-q\", Quit, None)]);\n\n        cx.spawn(|cx| async move {\n            cx.open_window(\n                WindowOptions {\n                    window_bounds: Some(WindowBounds::Windowed(Bounds::centered(\n                        None,\n                        size(px(600.), px(400.)),\n                        cx,\n                    ))),\n                    ..Default::default()\n                },\n                |window, cx| {\n                    let view = cx.new(|cx| ColorMixDemo::new(cx));\n                    cx.new(|cx| Root::new(view, window, cx))\n                },\n            )\n            .unwrap();\n        })\n        .detach();\n    });\n}\n"
  },
  {
    "path": "examples/dialog_overlay/Cargo.toml",
    "content": "[package]\nname = \"dialog_overlay\"\ndescription = \"An example of using gpui-component to create a Dialog with overlay.\"\nversion = \"0.5.1\"\npublish = false\nedition.workspace = true\n\n[dependencies]\nanyhow.workspace = true\ngpui.workspace = true\ngpui_platform.workspace = true\ngpui-component.workspace = true\ngpui-component-assets.workspace = true\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "examples/dialog_overlay/src/main.rs",
    "content": "use gpui::*;\nuse gpui_component::{button::*, menu::ContextMenuExt, *};\nuse gpui_component_assets::Assets;\n\nactions!(class_menu, [Open, Delete, Export, Info]);\n\npub struct HelloWorld;\n\nimpl HelloWorld {\n    fn show_dialog(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {\n        window.open_dialog(cx, move |dialog, _, _| {\n            dialog.title(\"Test dialog\").child(\"Hello from dialog!\")\n        });\n    }\n\n    fn show_drawer(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {\n        window.open_sheet(cx, move |drawer, _, _| {\n            drawer.title(\"Test Drawer\").child(\"Hello from Drawer!\")\n        });\n    }\n}\n\nimpl Render for HelloWorld {\n    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .bg(gpui::white())\n            .size_full()\n            .child(TitleBar::new().child(\"dialog & Drawer\"))\n            .child(\n                div()\n                    .p_8()\n                    .v_flex()\n                    .gap_2()\n                    .size_full()\n                    .child(\n                        h_flex()\n                            .gap_4()\n                            .child(\n                                Button::new(\"btn1\")\n                                    .outline()\n                                    .label(\"Open dialog\")\n                                    .on_click(cx.listener(Self::show_dialog)),\n                            )\n                            .child(\n                                Button::new(\"btn2\")\n                                    .outline()\n                                    .label(\"Open Drawer\")\n                                    .on_click(cx.listener(Self::show_drawer)),\n                            ),\n                    )\n                    .child(\n                        div()\n                            .id(\"second-area\")\n                            .v_flex()\n                            .h_40()\n                            .border_1()\n                            .border_dashed()\n                            .border_color(gpui::black())\n                            .items_center()\n                            .justify_center()\n                            .hover(|this| this.bg(gpui::yellow().opacity(0.2)))\n                            .child(\"Hover test here.\")\n                            .child(\"Right click to show Context Menu\")\n                            .context_menu({\n                                move |this, _, _| {\n                                    this.separator()\n                                        .menu(\"Open\", Box::new(Open))\n                                        .menu(\"Delete\", Box::new(Delete))\n                                        .menu(\"Export\", Box::new(Export))\n                                        .menu(\"Info\", Box::new(Info))\n                                        .separator()\n                                }\n                            }),\n                    ),\n            )\n            .children(Root::render_dialog_layer(window, cx))\n            .children(Root::render_sheet_layer(window, cx))\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application().with_assets(Assets);\n\n    app.run(move |cx| {\n        gpui_component::init(cx);\n\n        cx.spawn(async move |cx| {\n            cx.open_window(\n                WindowOptions {\n                    titlebar: Some(TitleBar::title_bar_options()),\n                    ..Default::default()\n                },\n                |window, cx| {\n                    let view = cx.new(|_| HelloWorld);\n                    // This first level on the window, should be a Root.\n                    cx.new(|cx| Root::new(view, window, cx))\n                },\n            )\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n"
  },
  {
    "path": "examples/focus_trap/Cargo.toml",
    "content": "[package]\nname = \"focus_trap\"\ndescription = \"Example demonstrating focus trap functionality.\"\nversion = \"0.5.0\"\npublish = false\nedition.workspace = true\n\n[dependencies]\nanyhow.workspace = true\ngpui.workspace = true\ngpui_platform.workspace = true\ngpui-component = { workspace = true }\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "examples/focus_trap/src/main.rs",
    "content": "use gpui::*;\nuse gpui_component::{button::*, h_flex, v_flex, *};\n\npub struct Example {\n    trap1_handle: FocusHandle,\n    trap2_handle: FocusHandle,\n}\nimpl Example {\n    fn new(cx: &mut App) -> Self {\n        Self {\n            trap1_handle: cx.focus_handle(),\n            trap2_handle: cx.focus_handle(),\n        }\n    }\n}\n\nimpl Render for Example {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .size_full()\n            .gap_6()\n            .p_8()\n            .child(div().text_xl().font_bold().child(\"Focus Trap Example\"))\n            .child(\n                div()\n                    .text_sm()\n                    .text_color(cx.theme().muted_foreground)\n                    .child(\"Press Tab to navigate between buttons. Notice how focus cycles within different areas.\"),\n            )\n            // Outside buttons - not in focus trap\n            .child(\n                v_flex()\n                    .gap_3()\n                    .child(\n                        div()\n                            .text_base()\n                            .font_semibold()\n                            .child(\"Outside Area (No Focus Trap)\"),\n                    )\n                    .child(\n                        h_flex()\n                            .gap_2()\n                            .child(Button::new(\"outside-1\").label(\"Outside Button 1\"))\n                            .child(Button::new(\"outside-2\").label(\"Outside Button 2\"))\n                            .child(Button::new(\"outside-3\").label(\"Outside Button 3\")),\n                    ),\n            )\n            // Focus trap area 1\n            .child(\n                v_flex()\n                    .gap_3()\n                    .child(div().text_base().font_semibold().child(\"Focus Trap Area 1\"))\n                    .child(\n                        h_flex()\n                            .gap_2()\n                            .p_4()\n                            .bg(cx.theme().secondary)\n                            .rounded(cx.theme().radius)\n                            .border_1()\n                            .border_color(cx.theme().border)\n                            .child(\n                                Button::new(\"trap1-1\")\n                                    .label(\"Trap 1 - Button 1\")\n                                    .on_click(|_, _, _| println!(\"Trap 1 - Button 1 clicked\")),\n                            )\n                            .child(\n                                Button::new(\"trap1-2\")\n                                    .label(\"Trap 1 - Button 2\")\n                                    .on_click(|_, _, _| println!(\"Trap 1 - Button 2 clicked\")),\n                            )\n                            .child(\n                                Button::new(\"trap1-3\")\n                                    .label(\"Trap 1 - Button 3\")\n                                    .on_click(|_, _, _| println!(\"Trap 1 - Button 3 clicked\")),\n                            )\n                            .focus_trap(\"trap1\", &self.trap1_handle),\n                    )\n                    .child(\n                        div()\n                            .text_xs()\n                            .text_color(cx.theme().muted_foreground)\n                            .child(\"→ Press Tab in this area, focus cycles through 3 buttons without escaping\"),\n                    ),\n            )\n            // Middle outside buttons\n            .child(\n                v_flex()\n                    .gap_3()\n                    .child(\n                        div()\n                            .text_base()\n                            .font_semibold()\n                            .child(\"Outside Area (No Focus Trap)\"),\n                    )\n                    .child(\n                        h_flex()\n                            .gap_2()\n                            .child(Button::new(\"outside-4\").label(\"Outside Button 4\"))\n                            .child(Button::new(\"outside-5\").label(\"Outside Button 5\")),\n                    ),\n            )\n            // Focus trap area 2\n            .child(\n                v_flex()\n                    .gap_3()\n                    .child(div().text_base().font_semibold().child(\"Focus Trap Area 2\"))\n                    .child(\n                        v_flex()\n                            .focus_trap(\"trap2\", &self.trap2_handle)\n                            .gap_2()\n                            .p_4()\n                            .grid()\n                            .grid_cols(4)\n                            .bg(cx.theme().accent.opacity(0.1))\n                            .rounded(cx.theme().radius)\n                            .border_1()\n                            .border_color(cx.theme().accent)\n                            .child(Button::new(\"trap2-1\").label(\"Trap 2 - Button 1\"))\n                            .child(Button::new(\"trap2-2\").label(\"Trap 2 - Button 2\"))\n                            .child(\n                                Button::new(\"trap2-3\").label(\"Trap 2 - Button 3\"),\n                            )\n                            .child(Button::new(\"trap2-4\").label(\"Trap 2 - Button 4\"))\n                    )\n                    .child(\n                        div()\n                            .text_xs()\n                            .text_color(cx.theme().muted_foreground)\n                            .child(\"→ Press Tab in this area, focus cycles through 4 buttons without escaping\"),\n                    ),\n            )\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application();\n\n    app.run(move |cx| {\n        gpui_component::init(cx);\n\n        let window_options = WindowOptions {\n            window_bounds: Some(WindowBounds::centered(size(px(800.), px(600.)), cx)),\n            ..Default::default()\n        };\n\n        cx.spawn(async move |cx| {\n            cx.open_window(window_options, |window, cx| {\n                let view = cx.new(|cx| Example::new(cx));\n                cx.new(|cx| Root::new(view, window, cx).bg(cx.theme().background))\n            })\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n"
  },
  {
    "path": "examples/hello_world/Cargo.toml",
    "content": "[package]\nname = \"hello_world\"\ndescription = \"A minimal example of application development with GPUI Component.\"\nversion = \"0.5.1\"\npublish = false\nedition.workspace = true\n\n[dependencies]\nanyhow.workspace = true\ngpui.workspace = true\ngpui_platform.workspace = true\ngpui-component = { workspace = true }\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "examples/hello_world/src/main.rs",
    "content": "use gpui::*;\nuse gpui_component::{button::*, *};\n\npub struct Example;\nimpl Render for Example {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        div()\n            .v_flex()\n            .gap_2()\n            .size_full()\n            .items_center()\n            .justify_center()\n            .child(\"Hello, World!\")\n            .child(\n                Button::new(\"ok\")\n                    .primary()\n                    .label(\"Let's Go!\")\n                    .on_click(|_, _, _| println!(\"Clicked!\")),\n            )\n    }\n}\n\nfn main() {\n    gpui_platform::application().run(move |cx| {\n        // This must be called before using any GPUI Component features.\n        gpui_component::init(cx);\n\n        cx.spawn(async move |cx| {\n            cx.open_window(WindowOptions::default(), |window, cx| {\n                let view = cx.new(|_| Example);\n                // This first level on the window, should be a Root.\n                cx.new(|cx| {\n                    // You can refine the root view style by yourself.\n                    Root::new(view, window, cx).bg(cx.theme().background)\n                })\n            })\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n"
  },
  {
    "path": "examples/input/Cargo.toml",
    "content": "[package]\nname = \"input\"\nversion = \"0.5.1\"\npublish = false\nedition.workspace = true\n\n[dependencies]\nanyhow.workspace = true\ngpui.workspace = true\ngpui_platform.workspace = true\ngpui-component.workspace = true\ngpui-component-assets.workspace = true\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "examples/input/src/main.rs",
    "content": "use gpui::*;\nuse gpui_component::{\n    input::{Input, InputEvent, InputState},\n    *,\n};\nuse gpui_component_assets::Assets;\n\npub struct Example {\n    input_state: Entity<InputState>,\n    display_text: SharedString,\n\n    /// We need to keep the subscriptions alive with the Example entity.\n    ///\n    /// So if the Example entity is dropped, the subscriptions are also dropped.\n    /// This is important to avoid memory leaks.\n    _subscriptions: Vec<Subscription>,\n}\n\nimpl Example {\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let input_state = cx.new(|cx| InputState::new(window, cx).placeholder(\"Enter your name\"));\n\n        let _subscriptions = vec![cx.subscribe_in(&input_state, window, {\n            let input_state = input_state.clone();\n            move |this, _, ev: &InputEvent, _window, cx| match ev {\n                InputEvent::Change => {\n                    let value = input_state.read(cx).value();\n                    this.display_text = format!(\"Hello, {}!\", value).into();\n                    cx.notify()\n                }\n                _ => {}\n            }\n        })];\n\n        Self {\n            input_state,\n            display_text: SharedString::default(),\n            _subscriptions,\n        }\n    }\n}\n\nimpl Render for Example {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .p_5()\n            .gap_2()\n            .size_full()\n            .items_center()\n            .justify_center()\n            .child(Input::new(&self.input_state))\n            .child(self.display_text.clone())\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application().with_assets(Assets);\n\n    app.run(move |cx| {\n        // This must be called before using any GPUI Component features.\n        gpui_component::init(cx);\n\n        let window_options = WindowOptions {\n            window_bounds: Some(WindowBounds::centered(size(px(800.), px(600.)), cx)),\n            ..Default::default()\n        };\n\n        cx.spawn(async move |cx| {\n            cx.open_window(window_options, |window, cx| {\n                let view = cx.new(|cx| Example::new(window, cx));\n                // This first level on the window, should be a Root.\n                cx.new(|cx| Root::new(view, window, cx))\n            })\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n"
  },
  {
    "path": "examples/system_monitor/Cargo.toml",
    "content": "[package]\nname = \"system_monitor\"\ndescription = \"A real-time system resource monitor using GPUI Component charts.\"\nversion = \"0.5.0\"\npublish = false\nedition = \"2024\"\n\n[dependencies]\nanyhow.workspace = true\ngpui.workspace = true\ngpui_platform.workspace = true\ngpui-component-assets.workspace = true\ngpui-component.workspace = true\nsysinfo = \"0.37\"\nsmol = \"2\"\nbattery = \"0.7\"\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\nmetal = \"0.33\"\ncore-foundation = \"0.10\"\n\n[target.'cfg(target_os = \"windows\")'.dependencies]\nwindows = { version = \"0.62\", features = [\n  \"Win32_Graphics_Direct3D\",\n  \"Win32_Graphics_Direct3D11\",\n  \"Win32_Graphics_Dxgi\",\n] }\n\n[lints.clippy]\ndbg_macro = \"deny\"\ntodo = \"deny\"\n"
  },
  {
    "path": "examples/system_monitor/src/main.rs",
    "content": "use std::collections::VecDeque;\nuse std::time::Duration;\n\nuse gpui::{actions, prelude::FluentBuilder as _, *};\nuse gpui_component::ThemeMode;\nuse gpui_component::{\n    ActiveTheme, Icon, IconName, Root, Sizable, Theme, TitleBar,\n    chart::AreaChart,\n    h_flex,\n    progress::Progress,\n    tab::{Tab, TabBar},\n    table::{Column, ColumnSort, DataTable, TableDelegate, TableState},\n    v_flex,\n};\nuse smol::Timer;\nuse sysinfo::{Disks, Pid, System};\n\n// Define the Quit action\nactions!(system_monitor, [Quit]);\n\nconst INTERVAL: Duration = Duration::from_millis(500);\nconst MAX_DATA_POINTS: usize = 120;\n\n/// Tab indices\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\nenum MonitorTab {\n    #[default]\n    System = 0,\n    Processes = 1,\n}\n\nimpl MonitorTab {\n    fn from_index(index: usize) -> Self {\n        match index {\n            0 => MonitorTab::System,\n            1 => MonitorTab::Processes,\n            _ => MonitorTab::System,\n        }\n    }\n}\n\n/// A single data point for system metrics\n#[derive(Clone)]\nstruct MetricPoint {\n    time: String,\n    cpu: f64,\n    memory: f64,\n}\n\n/// Process info for display\n#[derive(Clone)]\nstruct ProcessInfo {\n    pid: Pid,\n    name: String,\n    cpu_usage: f32,\n    memory: u64,\n}\n\n/// Disk info for display\n#[derive(Clone)]\nstruct DiskInfo {\n    #[allow(dead_code)]\n    name: String,\n    total: u64,\n    used: u64,\n}\n\n/// Battery info for display\n#[derive(Clone)]\nstruct BatteryInfo {\n    #[allow(dead_code)]\n    model: String,\n    icon: IconName,\n    percentage: f32,\n}\n\n/// Sort field for processes\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\nenum ProcessSortField {\n    Pid,\n    Name,\n    #[default]\n    Cpu,\n    Memory,\n}\n\n/// Process table delegate\nstruct ProcessTableDelegate {\n    processes: Vec<ProcessInfo>,\n    columns: Vec<Column>,\n    sort_field: ProcessSortField,\n    sort_order: ColumnSort,\n}\n\nimpl ProcessTableDelegate {\n    fn new() -> Self {\n        Self {\n            processes: Vec::new(),\n            columns: vec![\n                Column::new(\"pid\", \"PID\").width(70.).sortable(),\n                Column::new(\"name\", \"Name\").width(380.).sortable(),\n                Column::new(\"cpu\", \"CPU %\")\n                    .width(80.)\n                    .sortable()\n                    .sort(ColumnSort::Descending),\n                Column::new(\"memory\", \"Memory\").width(100.).sortable(),\n            ],\n            sort_field: ProcessSortField::Cpu,\n            sort_order: ColumnSort::Descending,\n        }\n    }\n\n    fn update_processes(&mut self, sys: &System) {\n        self.processes = sys\n            .processes()\n            .iter()\n            .map(|(pid, process)| ProcessInfo {\n                pid: *pid,\n                name: process.name().to_string_lossy().to_string(),\n                cpu_usage: process.cpu_usage(),\n                memory: process.memory(),\n            })\n            .collect();\n\n        self.sort_processes();\n    }\n\n    fn sort_processes(&mut self) {\n        let is_descending = matches!(self.sort_order, ColumnSort::Descending);\n\n        match self.sort_field {\n            ProcessSortField::Pid => {\n                self.processes.sort_by(|a, b| {\n                    let cmp = a.pid.as_u32().cmp(&b.pid.as_u32());\n                    if is_descending { cmp.reverse() } else { cmp }\n                });\n            }\n            ProcessSortField::Name => {\n                self.processes.sort_by(|a, b| {\n                    let cmp = a.name.to_lowercase().cmp(&b.name.to_lowercase());\n                    if is_descending { cmp.reverse() } else { cmp }\n                });\n            }\n            ProcessSortField::Cpu => {\n                self.processes.sort_by(|a, b| {\n                    let cmp = a\n                        .cpu_usage\n                        .partial_cmp(&b.cpu_usage)\n                        .unwrap_or(std::cmp::Ordering::Equal);\n                    if is_descending { cmp.reverse() } else { cmp }\n                });\n            }\n            ProcessSortField::Memory => {\n                self.processes.sort_by(|a, b| {\n                    let cmp = a.memory.cmp(&b.memory);\n                    if is_descending { cmp.reverse() } else { cmp }\n                });\n            }\n        }\n\n        // Keep top 200 processes\n        self.processes.truncate(200);\n    }\n}\n\nimpl TableDelegate for ProcessTableDelegate {\n    fn columns_count(&self, _cx: &App) -> usize {\n        self.columns.len()\n    }\n\n    fn rows_count(&self, _cx: &App) -> usize {\n        self.processes.len()\n    }\n\n    fn column(&self, col_ix: usize, _cx: &App) -> Column {\n        self.columns[col_ix].clone()\n    }\n\n    fn render_td(\n        &mut self,\n        row_ix: usize,\n        col_ix: usize,\n        _window: &mut Window,\n        cx: &mut Context<TableState<Self>>,\n    ) -> impl IntoElement {\n        let Some(process) = self.processes.get(row_ix) else {\n            return div().into_any_element();\n        };\n\n        match col_ix {\n            0 => div()\n                .text_xs()\n                .text_color(cx.theme().muted_foreground)\n                .child(format!(\"{}\", process.pid))\n                .into_any_element(),\n            1 => div()\n                .text_sm()\n                .text_color(cx.theme().foreground)\n                .truncate()\n                .child(process.name.clone())\n                .into_any_element(),\n            2 => div()\n                .text_xs()\n                .text_color(if process.cpu_usage > 50.0 {\n                    cx.theme().red\n                } else if process.cpu_usage > 20.0 {\n                    cx.theme().yellow\n                } else {\n                    cx.theme().blue\n                })\n                .child(format!(\"{:.1}%\", process.cpu_usage))\n                .into_any_element(),\n            3 => div()\n                .text_xs()\n                .text_color(cx.theme().green)\n                .child(format_bytes(process.memory))\n                .into_any_element(),\n            _ => div().into_any_element(),\n        }\n    }\n\n    fn perform_sort(\n        &mut self,\n        col_ix: usize,\n        sort: ColumnSort,\n        _window: &mut Window,\n        _cx: &mut Context<TableState<Self>>,\n    ) {\n        self.sort_order = sort;\n        self.sort_field = match col_ix {\n            0 => ProcessSortField::Pid,\n            1 => ProcessSortField::Name,\n            2 => ProcessSortField::Cpu,\n            3 => ProcessSortField::Memory,\n            _ => ProcessSortField::Cpu,\n        };\n        self.sort_processes();\n    }\n}\n\n/// Format bytes to human readable string\nfn format_bytes(bytes: u64) -> String {\n    const KB: u64 = 1024;\n    const MB: u64 = KB * 1024;\n    const GB: u64 = MB * 1024;\n\n    if bytes >= GB {\n        format!(\"{:.1} GB\", bytes as f64 / GB as f64)\n    } else if bytes >= MB {\n        format!(\"{:.1} MB\", bytes as f64 / MB as f64)\n    } else if bytes >= KB {\n        format!(\"{:.1} KB\", bytes as f64 / KB as f64)\n    } else {\n        format!(\"{} B\", bytes)\n    }\n}\n\n/// System monitor that collects and displays real-time metrics\npub struct SystemMonitor {\n    sys: System,\n    disks: Disks,\n    data: VecDeque<MetricPoint>,\n    time_index: usize,\n    active_tab: MonitorTab,\n    process_table: Entity<TableState<ProcessTableDelegate>>,\n    disk_info: Vec<DiskInfo>,\n    battery_info: Vec<BatteryInfo>,\n}\n\nimpl SystemMonitor {\n    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {\n        let mut sys = System::new_all();\n        sys.refresh_all();\n\n        let disks = Disks::new_with_refreshed_list();\n\n        // Create process table\n        let process_delegate = ProcessTableDelegate::new();\n        let process_table = cx.new(|cx| {\n            TableState::new(process_delegate, window, cx)\n                .col_selectable(false)\n                .col_movable(false)\n        });\n\n        let mut monitor = Self {\n            sys,\n            disks,\n            data: VecDeque::with_capacity(MAX_DATA_POINTS),\n            time_index: 0,\n            active_tab: MonitorTab::System,\n            process_table,\n            disk_info: Vec::new(),\n            battery_info: Vec::new(),\n        };\n\n        // Collect initial data\n        monitor.collect_metrics(cx);\n\n        // Start the update loop\n        cx.spawn(async move |this, cx| {\n            loop {\n                Timer::after(INTERVAL).await;\n\n                let result = this.update(cx, |this, cx| {\n                    this.collect_metrics(cx);\n                    cx.notify();\n                });\n\n                if result.is_err() {\n                    break;\n                }\n            }\n        })\n        .detach();\n\n        monitor\n    }\n\n    fn collect_metrics(&mut self, cx: &mut Context<Self>) {\n        // Refresh system info\n        self.sys.refresh_all();\n        self.disks.refresh(true);\n\n        // Calculate CPU usage\n        let cpu_usage = self.sys.global_cpu_usage() as f64;\n\n        // Calculate memory usage\n        let total_memory = self.sys.total_memory() as f64;\n        let used_memory = self.sys.used_memory() as f64;\n        let memory_usage = if total_memory > 0.0 {\n            (used_memory / total_memory * 100.0).min(100.0)\n        } else {\n            0.0\n        };\n\n        // Create data point\n        let point = MetricPoint {\n            time: format!(\"{}s\", self.time_index),\n            cpu: cpu_usage,\n            memory: memory_usage,\n        };\n\n        // Add to history\n        if self.data.len() >= MAX_DATA_POINTS {\n            self.data.pop_front();\n        }\n        self.data.push_back(point);\n        self.time_index += 1;\n\n        // Update process table\n        self.process_table.update(cx, |table, cx| {\n            table.delegate_mut().update_processes(&self.sys);\n            cx.notify();\n        });\n\n        // Update disk info (take first disk for status bar)\n        self.disk_info = self\n            .disks\n            .iter()\n            .map(|disk| DiskInfo {\n                name: disk.name().to_string_lossy().to_string(),\n                total: disk.total_space(),\n                used: disk.total_space() - disk.available_space(),\n            })\n            .collect();\n\n        // Update battery info\n        self.update_battery_info();\n    }\n\n    fn update_battery_info(&mut self) {\n        self.battery_info.clear();\n\n        if let Ok(manager) = battery::Manager::new()\n            && let Ok(batteries) = manager.batteries()\n        {\n            for battery in batteries.flatten() {\n                let icon = match battery.state() {\n                    battery::State::Charging => IconName::BatteryCharging,\n                    battery::State::Discharging => IconName::BatteryMedium,\n                    battery::State::Full => IconName::BatteryFull,\n                    battery::State::Empty => IconName::Battery,\n                    _ => IconName::Battery,\n                };\n\n                self.battery_info.push(BatteryInfo {\n                    model: battery\n                        .model()\n                        .map(|s| s.to_string())\n                        .unwrap_or_else(|| \"Battery\".to_string()),\n                    icon,\n                    percentage: battery.state_of_charge().value * 100.0,\n                });\n            }\n        }\n    }\n\n    fn set_active_tab(&mut self, index: usize, _window: &mut Window, cx: &mut Context<Self>) {\n        self.active_tab = MonitorTab::from_index(index);\n        cx.notify();\n    }\n\n    fn render_chart(\n        &self,\n        title: &str,\n        data: Vec<MetricPoint>,\n        value_fn: impl Fn(&MetricPoint) -> f64 + 'static,\n        color: Hsla,\n        cx: &Context<Self>,\n    ) -> impl IntoElement {\n        v_flex()\n            .min_h(px(160.))\n            .flex_1()\n            .gap_2()\n            .border_1()\n            .border_color(cx.theme().border)\n            .child(\n                h_flex()\n                    .justify_between()\n                    .py_1()\n                    .px_3()\n                    .child(\n                        div()\n                            .text_sm()\n                            .text_color(cx.theme().foreground)\n                            .child(title.to_string()),\n                    )\n                    .child({\n                        let current_value = data.last().map(&value_fn).unwrap_or(0.0);\n                        div()\n                            .text_sm()\n                            .text_color(color)\n                            .child(format!(\"{:.1}%\", current_value))\n                    }),\n            )\n            .child(\n                AreaChart::new(data)\n                    .x(|d| d.time.clone())\n                    .y(value_fn)\n                    .stroke(color)\n                    .fill(linear_gradient(\n                        0.,\n                        linear_color_stop(color.opacity(0.4), 1.),\n                        linear_color_stop(cx.theme().background.opacity(0.1), 0.),\n                    ))\n                    .tick_margin(15),\n            )\n    }\n\n    fn render_system_tab(&self, cx: &Context<Self>) -> impl IntoElement {\n        let data: Vec<MetricPoint> = self.data.iter().cloned().collect();\n        v_flex()\n            .p_3()\n            .gap_4()\n            .flex_1()\n            .child(self.render_chart(\"CPU Usage\", data.clone(), |d| d.cpu, cx.theme().red, cx))\n            .child(self.render_chart(\n                \"Memory Usage\",\n                data.clone(),\n                |d| d.memory,\n                cx.theme().blue,\n                cx,\n            ))\n    }\n\n    fn render_processes_tab(&self, _cx: &Context<Self>) -> impl IntoElement {\n        v_flex().size_full().child(\n            DataTable::new(&self.process_table)\n                .bordered(false)\n                .stripe(true)\n                .small(),\n        )\n    }\n\n    fn render_status_bar(&self, cx: &Context<Self>) -> impl IntoElement {\n        let primary_disk = self.disk_info.first();\n        let primary_battery = self.battery_info.first();\n\n        h_flex()\n            .px_3()\n            .gap_4()\n            .h_7()\n            .text_sm()\n            .items_center()\n            .justify_between()\n            .border_t_1()\n            .border_color(cx.theme().border)\n            .bg(cx.theme().tab_bar)\n            .text_color(cx.theme().muted_foreground)\n            .child(\n                h_flex()\n                    .gap_4()\n                    // Disk info\n                    .when_some(primary_disk, |this, disk| {\n                        let used_percent = if disk.total > 0 {\n                            (disk.used as f64 / disk.total as f64 * 100.0) as f32\n                        } else {\n                            0.0\n                        };\n                        this.child(\n                            h_flex()\n                                .gap_2()\n                                .w(px(135.))\n                                .items_center()\n                                .child(Icon::new(IconName::HardDrive))\n                                .child(\n                                    Progress::new(\"status-disk\")\n                                        .w_12()\n                                        .h_2()\n                                        .value(used_percent),\n                                )\n                                .child(format!(\"{:.0}%\", used_percent)),\n                        )\n                    })\n                    // Memory info\n                    .child({\n                        let mem_percent = self.data.back().map(|p| p.memory as f32).unwrap_or(0.0);\n                        h_flex()\n                            .gap_2()\n                            .w(px(135.))\n                            .items_center()\n                            .child(Icon::new(IconName::MemoryStick))\n                            .child(Progress::new(\"status-mem\").w_12().h_2().value(mem_percent))\n                            .child(format!(\"{:.0}%\", mem_percent))\n                    })\n                    // CPU info\n                    .child({\n                        let cpu_percent = self.data.back().map(|p| p.cpu as f32).unwrap_or(0.0);\n                        h_flex()\n                            .gap_2()\n                            .w(px(135.))\n                            .items_center()\n                            .child(Icon::new(IconName::Cpu))\n                            .child(Progress::new(\"status-cpu\").w_12().h_2().value(cpu_percent))\n                            .child(format!(\"{:.0}%\", cpu_percent))\n                    }),\n            )\n            .child(\n                // Battery info\n                div().when_some(primary_battery, |this, battery| {\n                    this.child(\n                        h_flex()\n                            .gap_2()\n                            .items_center()\n                            .child(Icon::new(battery.icon.clone()))\n                            .child(format!(\"{:.0}%\", battery.percentage)),\n                    )\n                }),\n            )\n    }\n}\n\nimpl Render for SystemMonitor {\n    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let active_tab_index = self.active_tab as usize;\n\n        v_flex()\n            .size_full()\n            .child(\n                TitleBar::new()\n                    .child(\n                        TabBar::new(\"monitor-tabs\")\n                            .mt(px(1.))\n                            .segmented()\n                            .px_0()\n                            .py(px(2.))\n                            .bg(cx.theme().title_bar)\n                            .selected_index(active_tab_index)\n                            .on_click(cx.listener(|this, ix: &usize, window, cx| {\n                                this.set_active_tab(*ix, window, cx);\n                            }))\n                            .child(Tab::new().label(\"System\"))\n                            .child(Tab::new().label(\"Processes\")),\n                    )\n                    .child(\n                        div()\n                            .mr_4()\n                            .text_xs()\n                            .text_color(cx.theme().muted_foreground)\n                            .child(format!(\n                                \"{:.1} GB\",\n                                self.sys.total_memory() as f64 / 1024.0 / 1024.0 / 1024.0\n                            )),\n                    ),\n            )\n            .bg(cx.theme().background)\n            .child(\n                div()\n                    .id(\"tab-content\")\n                    .flex_1()\n                    .overflow_y_scroll()\n                    .map(|this| match self.active_tab {\n                        MonitorTab::System => this.child(self.render_system_tab(cx)),\n                        MonitorTab::Processes => this.child(self.render_processes_tab(cx)),\n                    }),\n            )\n            .child(self.render_status_bar(cx))\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application().with_assets(gpui_component_assets::Assets);\n\n    app.run(move |cx| {\n        gpui_component::init(cx);\n\n        cx.bind_keys([\n            #[cfg(target_os = \"macos\")]\n            KeyBinding::new(\"cmd-q\", Quit, None),\n            #[cfg(not(target_os = \"macos\"))]\n            KeyBinding::new(\"alt-f4\", Quit, None),\n        ]);\n\n        // Handle the Quit action\n        cx.on_action(|_: &Quit, cx: &mut App| {\n            cx.quit();\n        });\n\n        let window_options = WindowOptions {\n            titlebar: Some(TitleBar::title_bar_options()),\n            window_bounds: Some(WindowBounds::centered(size(px(680.), px(600.)), cx)),\n            ..Default::default()\n        };\n\n        cx.spawn(async move |cx| {\n            cx.open_window(window_options, |window, cx| {\n                window.activate_window();\n                window.set_window_title(\"System Monitor\");\n\n                Theme::change(ThemeMode::Dark, Some(window), cx);\n\n                let view = cx.new(|cx| SystemMonitor::new(window, cx));\n                cx.new(|cx| Root::new(view, window, cx))\n            })\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n"
  },
  {
    "path": "examples/webview/Cargo.toml",
    "content": "[package]\nname = \"webview\"\ndescription = \"A minimal example of webview inside the GPUI application.\"\nversion = \"0.5.0\"\npublish = false\nedition.workspace = true\n\n[features]\ninspector = []\n\n[dependencies]\nanyhow.workspace = true\ngpui.workspace = true\ngpui_platform.workspace = true\ngpui-component = { workspace = true }\n\n# WebView dependencies\ngpui-wry = { path = \"../../crates/webview\" }\nwry = { version = \"0.53.3\", package = \"lb-wry\" }\nraw-window-handle = { version = \"0.6\", features = [\"std\"] }\n\n[target.\"cfg(target_os = \\\"linux\\\")\".dependencies]\ngtk = \"0.18.2\"\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "examples/webview/src/main.rs",
    "content": "use gpui::*;\nuse gpui_component::{\n    ActiveTheme as _, Root, h_flex,\n    input::{Input, InputEvent, InputState},\n    v_flex,\n};\nuse gpui_wry::WebView;\n\npub struct Example {\n    focus_handle: FocusHandle,\n    webview: Entity<WebView>,\n    address_input: Entity<InputState>,\n}\n\nimpl Example {\n    pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {\n        let webview = cx.new(|cx| {\n            let builder = wry::WebViewBuilder::new();\n            #[cfg(any(debug_assertions, feature = \"inspector\"))]\n            let builder = builder.with_devtools(true);\n\n            #[cfg(not(any(\n                target_os = \"windows\",\n                target_os = \"macos\",\n                target_os = \"ios\",\n                target_os = \"android\"\n            )))]\n            let webview = {\n                use gtk::prelude::*;\n                use wry::WebViewBuilderExtUnix;\n                // borrowed from https://github.com/tauri-apps/wry/blob/dev/examples/gtk_multiwebview.rs\n                // doesn't work yet\n                // TODO: How to initialize this fixed?\n                let fixed = gtk::Fixed::builder().build();\n                fixed.show_all();\n                builder.build_gtk(&fixed).unwrap()\n            };\n            #[cfg(any(\n                target_os = \"windows\",\n                target_os = \"macos\",\n                target_os = \"ios\",\n                target_os = \"android\"\n            ))]\n            let webview = {\n                use raw_window_handle::HasWindowHandle;\n\n                let window_handle = window.window_handle().expect(\"No window handle\");\n                builder.build_as_child(&window_handle).unwrap()\n            };\n\n            WebView::new(webview, window, cx)\n        });\n\n        let address_input = cx.new(|cx| {\n            InputState::new(window, cx).default_value(\"https://longbridge.github.io/gpui-component\")\n        });\n\n        let url = address_input.read(cx).value().clone();\n        webview.update(cx, |view, _| {\n            view.load_url(&url);\n        });\n\n        cx.new(|cx| {\n            let this = Self {\n                focus_handle: cx.focus_handle(),\n                webview,\n                address_input: address_input.clone(),\n            };\n\n            cx.subscribe(\n                &address_input,\n                |this: &mut Self, input, event: &InputEvent, cx| match event {\n                    InputEvent::PressEnter { .. } => {\n                        let url = input.read(cx).value().clone();\n                        this.webview.update(cx, |view, _| {\n                            view.load_url(&url);\n                        });\n                    }\n                    _ => {}\n                },\n            )\n            .detach();\n\n            this\n        })\n    }\n\n    pub fn hide(&self, _: &mut Window, cx: &mut App) {\n        self.webview.update(cx, |webview, _| webview.hide())\n    }\n\n    #[allow(unused)]\n    fn go_back(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {\n        self.webview.update(cx, |webview, _| {\n            webview.back().unwrap();\n        });\n    }\n}\n\nimpl Focusable for Example {\n    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {\n        self.focus_handle.clone()\n    }\n}\n\nimpl Render for Example {\n    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {\n        let webview = self.webview.clone();\n\n        v_flex()\n            .p_2()\n            .gap_3()\n            .size_full()\n            .child(\n                h_flex()\n                    .gap_2()\n                    .items_center()\n                    .child(Input::new(&self.address_input)),\n            )\n            .child(\n                div()\n                    .flex_1()\n                    .border_1()\n                    .h(gpui::px(400.))\n                    .border_color(cx.theme().border)\n                    .child(webview.clone()),\n            )\n    }\n}\n\nfn main() {\n    // Required this for Windows to render the WebView.\n    #[cfg(target_os = \"windows\")]\n    unsafe {\n        std::env::set_var(\"GPUI_DISABLE_DIRECT_COMPOSITION\", \"true\");\n    }\n\n    gpui_platform::application().run(move |cx| {\n        // This must be called before using any GPUI Component features.\n        gpui_component::init(cx);\n\n        cx.spawn(async move |cx| {\n            cx.open_window(WindowOptions::default(), |window, cx| {\n                let view = Example::new(window, cx);\n                cx.new(|cx| Root::new(view, window, cx))\n            })\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n"
  },
  {
    "path": "examples/window_title/Cargo.toml",
    "content": "[package]\nname = \"window_title\"\ndescription = \"An example of using gpui-component to create a window with a custom title bar.\"\nversion = \"0.5.1\"\npublish = false\nedition.workspace = true\n\n[dependencies]\nanyhow.workspace = true\ngpui.workspace = true\ngpui_platform.workspace = true\ngpui-component.workspace = true\ngpui-component-assets.workspace = true\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "examples/window_title/src/main.rs",
    "content": "use gpui::*;\nuse gpui_component::{\n    Root, TitleBar,\n    button::{Button, ButtonVariants},\n    h_flex, v_flex,\n};\n\npub struct Example;\nimpl Render for Example {\n    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {\n        v_flex()\n            .size_full()\n            .child(\n                // Render custom title bar on top of Root view.\n                TitleBar::new().child(\n                    h_flex()\n                        .w_full()\n                        .pr_2()\n                        .justify_between()\n                        .child(\"App with Custom title bar\")\n                        .child(\"Right Item\"),\n                ),\n            )\n            .child(\n                div()\n                    .id(\"window-body\")\n                    .p_5()\n                    .size_full()\n                    .items_center()\n                    .justify_center()\n                    .child(\"Hello, World!\")\n                    .child(\n                        Button::new(\"ok\")\n                            .primary()\n                            .label(\"Let's Go!\")\n                            .on_click(|_, _, _| println!(\"Clicked!\")),\n                    ),\n            )\n    }\n}\n\nfn main() {\n    let app = gpui_platform::application().with_assets(gpui_component_assets::Assets);\n\n    app.run(move |cx| {\n        gpui_component::init(cx);\n\n        cx.spawn(async move |cx| {\n            let window_options = WindowOptions {\n                // Setup GPUI to use custom title bar\n                titlebar: Some(TitleBar::title_bar_options()),\n                ..Default::default()\n            };\n\n            cx.open_window(window_options, |window, cx| {\n                let view = cx.new(|_| Example);\n                cx.new(|cx| Root::new(view, window, cx))\n            })\n            .expect(\"Failed to open window\");\n        })\n        .detach();\n    });\n}\n"
  },
  {
    "path": "flake.nix",
    "content": "{\n  description = \"gpui-component\";\n\n  inputs = {\n    nixpkgs.url      = \"github:NixOS/nixpkgs/nixos-unstable\";\n    rust-overlay.url = \"github:oxalica/rust-overlay\";\n    flake-utils.url  = \"github:numtide/flake-utils\";\n  };\n\n  outputs = { self, nixpkgs, rust-overlay, flake-utils, ... }:\n    flake-utils.lib.eachDefaultSystem (system:\n      let\n        overlays = [ (import rust-overlay) ];\n        pkgs = import nixpkgs {\n          inherit system overlays;\n        };\n      in\n      {\n        devShells.default = with pkgs; mkShell {\n          buildInputs = [\n            openssl\n            pkg-config\n            xorg.libX11\n            glib\n            pango\n            atkmm\n            gdk-pixbuf\n            gtk3\n            libsoup_3\n            webkitgtk_4_1\n            libxkbcommon\n            vulkan-loader\n            (rust-bin.beta.latest.default.override {\n              extensions = [ \"rust-src\" ];\n            })\n          ];\n\n          env = {\n            RUST_BACKTRACE = \"1\";\n            LD_LIBRARY_PATH = lib.makeLibraryPath [ vulkan-loader ];\n          };\n        };\n      }\n    );\n}\n"
  },
  {
    "path": "script/bootstrap",
    "content": "#!/usr/bin/env bash\n\nif [[ \"$OSTYPE\" == \"linux-gnu\"* ]]; then\n  echo \"Install Linux dependencies...\"\n  script/install-linux.sh\nelse\n  echo \"Install macOS dependencies...\"\nfi\n"
  },
  {
    "path": "script/bump-version.sh",
    "content": "#!/bin/bash\n\n# Colors\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nMAGENTA='\\033[0;35m'\nCYAN='\\033[0;36m'\nBOLD='\\033[1m'\nRESET='\\033[0m'\n\n# Check if version argument is provided\nnew_version=$1\nif [ -z \"$new_version\" ]\nthen\n  echo -e \"${RED}${BOLD}Error:${RESET} Version argument is required\"\n  echo -e \"${YELLOW}USAGE:${RESET} ./bump.sh [VERSION]\"\n  exit 1\nfi\n\n# Logging functions\nfunction log_header() {\n  local message=$1\n  echo \"\"\n  echo -e \"${BOLD}${BLUE}╔════════════════════════════════════════════════════════╗${RESET}\"\n  echo -e \"${BOLD}${BLUE}║${RESET}  ${CYAN}${BOLD}$message${RESET}\"\n  echo -e \"${BOLD}${BLUE}╚════════════════════════════════════════════════════════╝${RESET}\"\n  echo \"\"\n}\n\nfunction log_step() {\n  local step=$1\n  local message=$2\n  echo -e \"${MAGENTA}${BOLD}[$step]${RESET} ${message}\"\n}\n\nfunction log_success() {\n  local message=$1\n  echo -e \"${GREEN}${BOLD}✓${RESET} ${message}\"\n}\n\nfunction log_info() {\n  local message=$1\n  echo -e \"${CYAN}ℹ${RESET} ${message}\"\n}\n\nfunction log_error() {\n  local message=$1\n  echo -e \"${RED}${BOLD}✗${RESET} ${message}\"\n}\n\n# Start release process\nlog_header \"Starting Release Process for v$new_version\"\n\n# Step 1: Update crates version\nlog_step \"1/4\" \"Updating crates to version ${BOLD}v$new_version${RESET}\"\nif cargo set-version \"$new_version\"; then\n  log_success \"Crates version updated successfully\"\nelse\n  log_error \"Failed to update crates version\"\n  exit 1\nfi\necho \"\"\n\n# Step 2: Stage changes\nlog_step \"2/4\" \"Staging modified files\"\nif git add -u .; then\n  log_success \"Files staged successfully\"\nelse\n  log_error \"Failed to stage files\"\n  exit 1\nfi\necho \"\"\n\n# Step 3: Create commit and tag\nlog_step \"3/4\" \"Creating commit and tag\"\nif git commit -m \"Bump v$new_version\"; then\n  log_success \"Commit created: ${BOLD}Bump v$new_version${RESET}\"\nelse\n  log_error \"Failed to create commit\"\n  exit 1\nfi\n\nif git tag \"v$new_version\"; then\n  log_success \"Tag created: ${BOLD}v$new_version${RESET}\"\nelse\n  log_error \"Failed to create tag\"\n  exit 1\nfi\necho \"\"\n\n# Step 4: Push to remote\nlog_step \"4/4\" \"Pushing tag to remote\"\nlog_info \"Pushing ${BOLD}v$new_version${RESET} to origin...\"\nif git push origin \"v$new_version\"; then\n  log_success \"Tag pushed to remote successfully\"\nelse\n  log_error \"Failed to push tag to remote\"\n  exit 1\nfi\necho \"\"\n\n# Success message\necho -e \"${GREEN}${BOLD}╔════════════════════════════════════════════════════════╗${RESET}\"\necho -e \"${GREEN}${BOLD}║${RESET}  ${BOLD}🚀 Release v$new_version standby!${RESET}\"\necho -e \"${GREEN}${BOLD}║${RESET}  ${GREEN}Let's ship it!${RESET}\"\necho -e \"${GREEN}${BOLD}╚════════════════════════════════════════════════════════╝${RESET}\"\necho \"\"\n"
  },
  {
    "path": "script/install-linux.sh",
    "content": "#!/usr/bin/env bash\n\nsudo apt update\n# Test on Ubuntu 24.04\nsudo apt install -y \\\n  gcc g++ clang libfontconfig-dev libwayland-dev \\\n  libwebkit2gtk-4.1-dev libxkbcommon-x11-dev libx11-xcb-dev \\\n  libssl-dev libzstd-dev \\\n  vulkan-validationlayers libvulkan1\n"
  },
  {
    "path": "script/install-window.ps1",
    "content": "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser\nInvoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression\n\nwinget install Microsoft.VisualStudio.2022.Community --silent --override \"--wait --quiet --add ProductLang En-us --add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended\"\nscoop bucket add extras\nscoop install cmake\n"
  },
  {
    "path": "themes/adventure.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Adventure\",\n  \"author\": \"iTerm2-Color-Schemes\",\n  \"url\": \"https://github.com/mbadolato/iTerm2-Color-Schemes\",\n  \"themes\": [\n    {\n      \"name\": \"Adventure\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#25292d\",\n        \"accent.foreground\": \"#feffff\",\n        \"background\": \"#040404\",\n        \"border\": \"#282828\",\n        \"danger.background\": \"#d84a33\",\n        \"foreground\": \"#feffff\",\n        \"input.border\": \"#333333\",\n        \"link.active.foreground\": \"#417AB3\",\n        \"link.foreground\": \"#417AB3\",\n        \"link.hover.foreground\": \"#417AB3\",\n        \"list.active.background\": \"#5da60222\",\n        \"list.active.border\": \"#5da602\",\n        \"list.even.background\": \"#0e0e0e\",\n        \"muted.background\": \"#171717\",\n        \"muted.foreground\": \"#5d6165\",\n        \"panel.background\": \"#003a5b\",\n        \"popover.background\": \"#040404\",\n        \"popover.foreground\": \"#feffff\",\n        \"primary.active.background\": \"#0265a799\",\n        \"primary.background\": \"#4384ad\",\n        \"primary.foreground\": \"#feffff\",\n        \"primary.hover.background\": \"#417AB3\",\n        \"scrollbar.background\": \"#003a5b00\",\n        \"scrollbar.thumb.background\": \"#606060\",\n        \"secondary.background\": \"#171a1c\",\n        \"secondary.active.background\": \"#191d1e\",\n        \"secondary.foreground\": \"#e4d5c7\",\n        \"secondary.hover.background\": \"#1e2224\",\n        \"tab.active.background\": \"#040404\",\n        \"tab.active.foreground\": \"#feffff\",\n        \"tab.background\": \"#04040400\",\n        \"tab.foreground\": \"#677179\",\n        \"tab_bar.background\": \"#040404\",\n        \"table.background\": \"#040404\",\n        \"table.head.foreground\": \"#feffffb3\",\n        \"table.row.border\": \"#4b647970\",\n        \"title_bar.background\": \"#0f1112\",\n        \"ring\": \"#5da602\",\n        \"base.red\": \"#d84a33\",\n        \"base.red.light\": \"#d76b42\",\n        \"base.green\": \"#5da602\",\n        \"base.green.light\": \"#99b52c\",\n        \"base.blue\": \"#417ab3\",\n        \"base.blue.light\": \"#97d7ef\",\n        \"base.cyan\": \"#41b3a9\",\n        \"base.cyan.light\": \"#97efd6\",\n        \"base.magenta\": \"#882252\",\n        \"base.magenta.light\": \"#e599bc\",\n        \"base.yellow\": \"#aa7900\",\n        \"base.yellow.light\": \"#ffb670\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#feffff\",\n        \"editor.background\": \"#040404\",\n        \"editor.active_line.background\": \"#0e0e0e\",\n        \"editor.line_number\": \"#5d6165\",\n        \"editor.active_line_number\": \"#feffff\",\n        \"editor.invisible\": \"#5d616566\",\n        \"conflict\": \"#d84a33\",\n        \"created\": \"#5da602\",\n        \"deleted\": \"#d84a33\",\n        \"error\": \"#d84a33\",\n        \"hidden\": \"#5d6165\",\n        \"hint\": \"#99b52c\",\n        \"ignored\": \"#5d6165\",\n        \"info\": \"#417ab3\",\n        \"modified\": \"#eebb6e\",\n        \"predictive\": \"#5d6165\",\n        \"renamed\": \"#5da602\",\n        \"success\": \"#5da602\",\n        \"unreachable\": \"#5d6165\",\n        \"warning\": \"#eebb6e\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#eebb6e\"\n          },\n          \"boolean\": {\n            \"color\": \"#5da602\"\n          },\n          \"comment\": {\n            \"color\": \"#5d6165\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#5d6165\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#d84a33\"\n          },\n          \"constructor\": {\n            \"color\": \"#eebb6e\"\n          },\n          \"embedded\": {\n            \"color\": \"#feffff\"\n          },\n          \"function\": {\n            \"color\": \"#5da602\"\n          },\n          \"keyword\": {\n            \"color\": \"#417ab3\"\n          },\n          \"link_text\": {\n            \"color\": \"#97d7ef\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#4b6479\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#d84a33\"\n          },\n          \"string\": {\n            \"color\": \"#5da602\"\n          },\n          \"string.escape\": {\n            \"color\": \"#5da602\"\n          },\n          \"string.regex\": {\n            \"color\": \"#5da602\"\n          },\n          \"string.special\": {\n            \"color\": \"#eebb6e\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#eebb6e\"\n          },\n          \"tag\": {\n            \"color\": \"#eebb6e\"\n          },\n          \"text.literal\": {\n            \"color\": \"#d84a33\"\n          },\n          \"title\": {\n            \"color\": \"#97d7ef\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#eebb6e\"\n          },\n          \"property\": {\n            \"color\": \"#feffff\"\n          },\n          \"variable.special\": {\n            \"color\": \"#d84a33\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Adventure Time\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#36345f\",\n        \"accent.foreground\": \"#C7C7D4\",\n        \"background\": \"#1f1d45\",\n        \"border\": \"#333150\",\n        \"foreground\": \"#C7C7D4\",\n        \"input.border\": \"#555465\",\n        \"link.active.foreground\": \"#4b61bf\",\n        \"link.foreground\": \"#5f72c6\",\n        \"link.hover.foreground\": \"#8593d3\",\n        \"list.active.background\": \"#4ab11822\",\n        \"list.active.border\": \"#549235\",\n        \"list.even.background\": \"#1c1a37\",\n        \"muted.background\": \"#29274a\",\n        \"muted.foreground\": \"#717192\",\n        \"panel.background\": \"#003a5b\",\n        \"popover.background\": \"#1f1d45\",\n        \"popover.foreground\": \"#C7C7D4\",\n        \"primary.active.background\": \"#5f72c699\",\n        \"primary.background\": \"#5f72c6\",\n        \"primary.foreground\": \"#ffffff\",\n        \"primary.hover.background\": \"#5f72c6aa\",\n        \"scrollbar.background\": \"#003a5b00\",\n        \"scrollbar.thumb.background\": \"#555465\",\n        \"secondary.background\": \"#2e2c51\",\n        \"secondary.active.background\": \"#36345f\",\n        \"secondary.foreground\": \"#C7C7D4\",\n        \"secondary.hover.background\": \"#34325b\",\n        \"tab.active.background\": \"#1f1d45\",\n        \"tab.active.foreground\": \"#C7C7D4\",\n        \"tab.background\": \"#1f1d4500\",\n        \"tab.foreground\": \"#aeab9e\",\n        \"tab_bar.background\": \"#1f1d45\",\n        \"table.active.background\": \"#4ab11811\",\n        \"table.active.border\": \"#549235\",\n        \"table.head.foreground\": \"#f8dcc0b3\",\n        \"table.row.border\": \"#333333\",\n        \"title_bar.background\": \"#1c1a3e\",\n        \"title_bar.border\": \"#333150\",\n        \"base.red\": \"#a02733\",\n        \"base.red.light\": \"#fc5f5a\",\n        \"base.green\": \"#549235\",\n        \"base.green.light\": \"#9eff6e\",\n        \"base.blue\": \"#2b53ab\",\n        \"base.blue.light\": \"#1997c6\",\n        \"base.cyan\": \"#26977b\",\n        \"base.cyan.light\": \"#5afcd4\",\n        \"base.magenta\": \"#665993\",\n        \"base.magenta.light\": \"#9b5953\",\n        \"base.yellow\": \"#ce7837\",\n        \"base.yellow.light\": \"#efc11a\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#f8dcc0\",\n        \"editor.background\": \"#1f1d45\",\n        \"editor.active_line.background\": \"#36345f\",\n        \"editor.line_number\": \"#8F8F8F\",\n        \"editor.active_line_number\": \"#DDDDDD\",\n        \"editor.invisible\": \"#5d616566\",\n        \"conflict\": \"#D2602D\",\n        \"created\": \"#3f72e2\",\n        \"hidden\": \"#9E9E9E\",\n        \"hint\": \"#b283f8\",\n        \"modified\": \"#B0A878\",\n        \"predictive\": \"#5D5945\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#be9a52\"\n          },\n          \"boolean\": {\n            \"color\": \"#E1D797\"\n          },\n          \"comment\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"constant\": {\n            \"color\": \"#E1D797\"\n          },\n          \"constructor\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"embedded\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"function\": {\n            \"color\": \"#E1D797\"\n          },\n          \"keyword\": {\n            \"color\": \"#E19773\"\n          },\n          \"link_text\": {\n            \"color\": \"#A86D3B\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#6F6D66\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#E19773\"\n          },\n          \"string\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.escape\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.regex\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.special\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#E1D797\"\n          },\n          \"tag\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"text.literal\": {\n            \"color\": \"#E1D797\"\n          },\n          \"title\": {\n            \"color\": \"#A76D3B\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#A86D3B\"\n          },\n          \"property\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"variable.special\": {\n            \"color\": \"#E19773\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/alduin.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Alduin\",\n  \"author\": \"AlessandroYorba\",\n  \"url\": \"https://github.com/AlessandroYorba/Alduin\",\n  \"themes\": [\n    {\n      \"name\": \"Alduin\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#2e2b29\",\n        \"accent.foreground\": \"#ebdbb2\",\n        \"background\": \"#1C1C1C\",\n        \"border\": \"#3a3a3a\",\n        \"danger.background\": \"#cc241d\",\n        \"danger.active.background\": \"#cc241d\",\n        \"danger.hover.background\": \"#fb4934\",\n        \"foreground\": \"#9E9E9E\",\n        \"input.border\": \"#504945\",\n        \"link.active.foreground\": \"#83a598\",\n        \"link.foreground\": \"#83a598\",\n        \"link.hover.foreground\": \"#83a598\",\n        \"list.active.background\": \"#262626\",\n        \"list.active.border\": \"#9E9E9E\",\n        \"list.even.background\": \"#262626\",\n        \"list.head.background\": \"#1C1C1C\",\n        \"muted.background\": \"#262626\",\n        \"muted.foreground\": \"#878787\",\n        \"panel.background\": \"#282828\",\n        \"primary.active.background\": \"#45858899\",\n        \"primary.background\": \"#458588\",\n        \"primary.foreground\": \"#F0F0F0\",\n        \"primary.hover.background\": \"#458588AA\",\n        \"scrollbar.background\": \"#28282800\",\n        \"scrollbar.thumb.background\": \"#504945\",\n        \"secondary.background\": \"#282828\",\n        \"secondary.active.background\": \"#35322f\",\n        \"secondary.foreground\": \"#9E9E9E\",\n        \"secondary.hover.background\": \"#36323099\",\n        \"tab.active.background\": \"#1C1C1C\",\n        \"tab.active.foreground\": \"#ebdbb2\",\n        \"tab.background\": \"#14141400\",\n        \"tab.foreground\": \"#848382\",\n        \"table.row.border\": \"#50494570\",\n        \"title_bar.background\": \"#262626\",\n        \"title_bar.border\": \"#3a3a3a\",\n        \"ring\": \"#9E9E9E\",\n        \"base.blue\": \"#87afaf\",\n        \"base.cyan\": \"#878787\",\n        \"base.green\": \"#7a875f\",\n        \"base.magenta\": \"#af8787\",\n        \"base.magenta.light\": \"#af878788\",\n        \"base.red\": \"#8b5f61\",\n        \"base.yellow\": \"#9d906c\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#DDDDDD\",\n        \"editor.background\": \"#000000\",\n        \"editor.active_line.background\": \"#131313\",\n        \"editor.line_number\": \"#8F8F8F\",\n        \"editor.active_line_number\": \"#DDDDDD\",\n        \"editor.invisible\": \"#9E9E9E66\",\n        \"conflict\": \"#8b5f61\",\n        \"created\": \"#87afaf\",\n        \"error\": \"#8b5f61\",\n        \"hidden\": \"#9E9E9E\",\n        \"hint\": \"#af8787\",\n        \"info\": \"#878787\",\n        \"modified\": \"#B0A878\",\n        \"predictive\": \"#5D5945\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#be9a52\"\n          },\n          \"boolean\": {\n            \"color\": \"#E1D797\"\n          },\n          \"comment\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"constant\": {\n            \"color\": \"#E1D797\"\n          },\n          \"constructor\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"embedded\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"function\": {\n            \"color\": \"#E1D797\"\n          },\n          \"keyword\": {\n            \"color\": \"#E19773\"\n          },\n          \"link_text\": {\n            \"color\": \"#A86D3B\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#6F6D66\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#E19773\"\n          },\n          \"string\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.escape\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.regex\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.special\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#E1D797\"\n          },\n          \"tag\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"text.literal\": {\n            \"color\": \"#E1D797\"\n          },\n          \"title\": {\n            \"color\": \"#A76D3B\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#A86D3B\"\n          },\n          \"property\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"variable.special\": {\n            \"color\": \"#E19773\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/asciinema.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Asciinema\",\n  \"author\": \"asciinema.org\",\n  \"url\": \"https://asciinema.org\",\n  \"themes\": [\n    {\n      \"name\": \"Asciinema\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#262829\",\n        \"accent.foreground\": \"#cccccc\",\n        \"background\": \"#121314\",\n        \"border\": \"#3a3a3a\",\n        \"danger.background\": \"#dd3c69\",\n        \"danger.active.background\": \"#dd3c69\",\n        \"danger.hover.background\": \"#e94f7a\",\n        \"foreground\": \"#cccccc\",\n        \"input.border\": \"#3a3a3a\",\n        \"link.active.foreground\": \"#26b0d7\",\n        \"link.foreground\": \"#26b0d7\",\n        \"link.hover.foreground\": \"#3cc0e7\",\n        \"list.active.background\": \"#1a1b1c\",\n        \"list.active.border\": \"#cccccc\",\n        \"list.even.background\": \"#1a1b1c\",\n        \"list.head.background\": \"#121314\",\n        \"muted.background\": \"#1a1b1c\",\n        \"muted.foreground\": \"#6d6d6d\",\n        \"panel.background\": \"#181919\",\n        \"primary.active.background\": \"#26b0d799\",\n        \"primary.background\": \"#26b0d7\",\n        \"primary.foreground\": \"#ffffff\",\n        \"primary.hover.background\": \"#26b0d7AA\",\n        \"scrollbar.background\": \"#12131400\",\n        \"scrollbar.thumb.background\": \"#2a2a2a\",\n        \"secondary.background\": \"#181919\",\n        \"secondary.active.background\": \"#1f2020\",\n        \"secondary.foreground\": \"#cccccc\",\n        \"secondary.hover.background\": \"#1f202099\",\n        \"tab.active.background\": \"#121314\",\n        \"tab.active.foreground\": \"#cccccc\",\n        \"tab.background\": \"#12131400\",\n        \"tab.foreground\": \"#6d6d6d\",\n        \"table.row.border\": \"#2a2a2a70\",\n        \"title_bar.background\": \"#1a1b1c\",\n        \"title_bar.border\": \"#3a3a3a\",\n        \"ring\": \"#5d5d5d\",\n        \"base.blue\": \"#26b0d7\",\n        \"base.cyan\": \"#54e1b9\",\n        \"base.green\": \"#4ebf22\",\n        \"base.magenta\": \"#b954e1\",\n        \"base.red\": \"#dd3c69\",\n        \"base.yellow\": \"#ddaf3c\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#cccccc\",\n        \"editor.background\": \"#121314\",\n        \"editor.active_line.background\": \"#1a1b1c\",\n        \"editor.line_number\": \"#6d6d6d\",\n        \"editor.active_line_number\": \"#cccccc\",\n        \"editor.invisible\": \"#6d6d6d66\",\n        \"conflict\": \"#dd3c69\",\n        \"created\": \"#4ebf22\",\n        \"error\": \"#dd3c69\",\n        \"hidden\": \"#6d6d6d\",\n        \"hint\": \"#b954e1\",\n        \"info\": \"#26b0d7\",\n        \"modified\": \"#ddaf3c\",\n        \"predictive\": \"#2a2a2a\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#ddaf3c\"\n          },\n          \"boolean\": {\n            \"color\": \"#b954e1\"\n          },\n          \"comment\": {\n            \"color\": \"#5d5d5d\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#5d5d5d\"\n          },\n          \"constant\": {\n            \"color\": \"#b954e1\"\n          },\n          \"constructor\": {\n            \"color\": \"#26b0d7\"\n          },\n          \"embedded\": {\n            \"color\": \"#cccccc\"\n          },\n          \"function\": {\n            \"color\": \"#54e1b9\"\n          },\n          \"keyword\": {\n            \"color\": \"#dd3c69\"\n          },\n          \"link_text\": {\n            \"color\": \"#26b0d7\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#54e1b9\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#b954e1\"\n          },\n          \"string\": {\n            \"color\": \"#4ebf22\"\n          },\n          \"string.escape\": {\n            \"color\": \"#54e1b9\"\n          },\n          \"string.regex\": {\n            \"color\": \"#4ebf22\"\n          },\n          \"string.special\": {\n            \"color\": \"#ddaf3c\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#ddaf3c\"\n          },\n          \"tag\": {\n            \"color\": \"#dd3c69\"\n          },\n          \"text.literal\": {\n            \"color\": \"#cccccc\"\n          },\n          \"title\": {\n            \"color\": \"#ddaf3c\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#26b0d7\"\n          },\n          \"property\": {\n            \"color\": \"#cccccc\"\n          },\n          \"variable.special\": {\n            \"color\": \"#dd3c69\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/ayu.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Ayu Light\",\n  \"author\": \"Zed Industries\",\n  \"url\": \"https://github.com/zed-industries/zed/blob/e62dd2a0e584154886ff86cc1e9e3e060558b977/assets/themes/ayu\",\n  \"themes\": [\n    {\n      \"name\": \"Ayu Light\",\n      \"mode\": \"light\",\n      \"colors\": {\n        \"accent.background\": \"#dadadc\",\n        \"accent.foreground\": \"#5C6773\",\n        \"background\": \"#FCFCFC\",\n        \"foreground\": \"#5c6166\",\n        \"border\": \"#cfd1d2ff\",\n        \"ring\": \"#FF9940\",\n        \"input.border\": \"#D9DCE3\",\n        \"list.active.background\": \"#55B4D422\",\n        \"list.active.border\": \"#55B4D4\",\n        \"list.even.background\": \"#E6E6E6\",\n        \"list.head.background\": \"#F4F4F599\",\n        \"muted.background\": \"#F4F4F5\",\n        \"muted.foreground\": \"#99a0a6\",\n        \"panel.background\": \"#F3F4F5\",\n        \"popover.background\": \"#ECECED\",\n        \"popover.foreground\": \"#5C6773\",\n        \"primary.active.background\": \"#42accf\",\n        \"primary.background\": \"#55b4d3\",\n        \"primary.foreground\": \"#FAFAFA\",\n        \"primary.hover.background\": \"#5fb9d7\",\n        \"scrollbar.background\": \"#FAFAFA00\",\n        \"scrollbar.thumb.background\": \"#5c61664c\",\n        \"secondary.active.background\": \"#CECECE\",\n        \"secondary.background\": \"#ECECED\",\n        \"secondary.foreground\": \"#5C6773\",\n        \"secondary.hover.background\": \"#e0e0e0\",\n        \"tab.active.background\": \"#FCFCFC\",\n        \"tab.active.foreground\": \"#5C6773\",\n        \"tab.background\": \"#ECECED00\",\n        \"tab_bar.background\": \"#F4F4F5\",\n        \"title_bar.background\": \"#ECECED\",\n        \"title_bar.border\": \"#CFD1D2\",\n        \"base.yellow\": \"#F1AD49\",\n        \"base.red\": \"#F07171\",\n        \"base.blue\": \"#55b4d3\",\n        \"base.green\": \"#85b304\",\n        \"base.magenta\": \"#9371f0\",\n        \"base.cyan\": \"#4dbf99\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#5C6773\",\n        \"editor.background\": \"#FCFCFC\",\n        \"editor.active_line.background\": \"#ECECED\",\n        \"editor.line_number\": \"#ABB0B6\",\n        \"editor.active_line_number\": \"#5C6773\",\n        \"editor.invisible\": \"#73777b66\",\n        \"conflict\": \"#f1ad49ff\",\n        \"conflict.background\": \"#ffeedaff\",\n        \"conflict.border\": \"#ffe1beff\",\n        \"created\": \"#85b304ff\",\n        \"created.background\": \"#e9efd2ff\",\n        \"created.border\": \"#d7e3aeff\",\n        \"deleted\": \"#ef7271ff\",\n        \"deleted.background\": \"#ffe3e1ff\",\n        \"deleted.border\": \"#ffcdcaff\",\n        \"error\": \"#ef7271ff\",\n        \"error.background\": \"#ffe3e1ff\",\n        \"error.border\": \"#ffcdcaff\",\n        \"hidden\": \"#a9acaeff\",\n        \"hidden.background\": \"#dcdddeff\",\n        \"hidden.border\": \"#d5d6d8ff\",\n        \"hint\": \"#8ca7c2ff\",\n        \"hint.background\": \"#deebfaff\",\n        \"hint.border\": \"#c4daf6ff\",\n        \"ignored\": \"#a9acaeff\",\n        \"ignored.background\": \"#dcdddeff\",\n        \"ignored.border\": \"#cfd1d2ff\",\n        \"info\": \"#3b9ee5ff\",\n        \"info.background\": \"#deebfaff\",\n        \"info.border\": \"#c4daf6ff\",\n        \"modified\": \"#f1ad49ff\",\n        \"modified.background\": \"#ffeedaff\",\n        \"modified.border\": \"#ffe1beff\",\n        \"predictive\": \"#9eb9d3ff\",\n        \"predictive.background\": \"#e9efd2ff\",\n        \"predictive.border\": \"#d7e3aeff\",\n        \"renamed\": \"#3b9ee5ff\",\n        \"renamed.background\": \"#deebfaff\",\n        \"renamed.border\": \"#c4daf6ff\",\n        \"success\": \"#85b304ff\",\n        \"success.background\": \"#e9efd2ff\",\n        \"success.border\": \"#d7e3aeff\",\n        \"unreachable\": \"#8b8e92ff\",\n        \"unreachable.background\": \"#dcdddeff\",\n        \"unreachable.border\": \"#cfd1d2ff\",\n        \"warning\": \"#f1ad49ff\",\n        \"warning.background\": \"#ffeedaff\",\n        \"warning.border\": \"#ffe1beff\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#3b9ee5ff\"\n          },\n          \"boolean\": {\n            \"color\": \"#a37accff\"\n          },\n          \"comment\": {\n            \"color\": \"#898d90ff\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#898d90ff\"\n          },\n          \"constant\": {\n            \"color\": \"#a37accff\"\n          },\n          \"constructor\": {\n            \"color\": \"#3b9ee5ff\"\n          },\n          \"embedded\": {\n            \"color\": \"#5c6166ff\"\n          },\n          \"emphasis\": {\n            \"color\": \"#3b9ee5ff\"\n          },\n          \"emphasis.strong\": {\n            \"color\": \"#3b9ee5ff\",\n            \"font_weight\": 700\n          },\n          \"enum\": {\n            \"color\": \"#f98d3fff\"\n          },\n          \"function\": {\n            \"color\": \"#f2ad48ff\"\n          },\n          \"hint\": {\n            \"color\": \"#8ca7c2ff\",\n            \"font_weight\": 700\n          },\n          \"keyword\": {\n            \"color\": \"#fa8d3eff\"\n          },\n          \"label\": {\n            \"color\": \"#3b9ee5ff\"\n          },\n          \"link_text\": {\n            \"color\": \"#f98d3fff\",\n            \"font_style\": \"italic\"\n          },\n          \"link_uri\": {\n            \"color\": \"#85b304ff\"\n          },\n          \"namespace\": {\n            \"color\": \"#5c6166ff\"\n          },\n          \"number\": {\n            \"color\": \"#a37accff\"\n          },\n          \"operator\": {\n            \"color\": \"#ed9365ff\"\n          },\n          \"predictive\": {\n            \"color\": \"#9eb9d3ff\",\n            \"font_style\": \"italic\"\n          },\n          \"preproc\": {\n            \"color\": \"#5c6166ff\"\n          },\n          \"primary\": {\n            \"color\": \"#5c6166ff\"\n          },\n          \"property\": {\n            \"color\": \"#3b9ee5ff\"\n          },\n          \"punctuation\": {\n            \"color\": \"#73777bff\"\n          },\n          \"punctuation.bracket\": {\n            \"color\": \"#73777bff\"\n          },\n          \"punctuation.delimiter\": {\n            \"color\": \"#73777bff\"\n          },\n          \"punctuation.list_marker\": {\n            \"color\": \"#73777bff\"\n          },\n          \"punctuation.markup\": {\n            \"color\": \"#73777bff\"\n          },\n          \"punctuation.special\": {\n            \"color\": \"#a37accff\"\n          },\n          \"selector\": {\n            \"color\": \"#a37accff\"\n          },\n          \"selector.pseudo\": {\n            \"color\": \"#3b9ee5ff\"\n          },\n          \"string\": {\n            \"color\": \"#86b300ff\"\n          },\n          \"string.escape\": {\n            \"color\": \"#898d90ff\"\n          },\n          \"string.regex\": {\n            \"color\": \"#4bbf98ff\"\n          },\n          \"string.special\": {\n            \"color\": \"#e6ba7eff\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#f98d3fff\"\n          },\n          \"tag\": {\n            \"color\": \"#3b9ee5ff\"\n          },\n          \"text.literal\": {\n            \"color\": \"#f98d3fff\"\n          },\n          \"title\": {\n            \"color\": \"#5c6166ff\",\n            \"font_weight\": 700\n          },\n          \"type\": {\n            \"color\": \"#389ee6ff\"\n          },\n          \"variable\": {\n            \"color\": \"#5c6166ff\"\n          },\n          \"variant\": {\n            \"color\": \"#3b9ee5ff\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Ayu Dark\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#20242b\",\n        \"accent.foreground\": \"#B3B1AD\",\n        \"background\": \"#0D1016\",\n        \"border\": \"#292a2c\",\n        \"ring\": \"#FFB454\",\n        \"foreground\": \"#B3B1AD\",\n        \"input.border\": \"#2D2F34\",\n        \"list.active.background\": \"#36A3D922\",\n        \"list.active.border\": \"#36A3D9\",\n        \"list.even.background\": \"#191F2A99\",\n        \"muted.background\": \"#16191F\",\n        \"muted.foreground\": \"#52514f\",\n        \"popover.background\": \"#0D1016\",\n        \"popover.foreground\": \"#B3B1AD\",\n        \"primary.active.background\": \"#36A3D9\",\n        \"primary.background\": \"#5ac1fe\",\n        \"primary.foreground\": \"#1F2430\",\n        \"primary.hover.background\": \"#3DAEE9\",\n        \"scrollbar.background\": \"#0A0E1400\",\n        \"scrollbar.thumb.background\": \"#bfbdb64c\",\n        \"secondary.active.background\": \"#26282f\",\n        \"secondary.background\": \"#1F2127\",\n        \"secondary.foreground\": \"#B3B1AD\",\n        \"secondary.hover.background\": \"#26282f99\",\n        \"tab.active.foreground\": \"#B3B1AD\",\n        \"tab.active.background\": \"#0D1016\",\n        \"tab_bar.background\": \"#16191F\",\n        \"title_bar.background\": \"#16191F\",\n        \"title_bar.border\": \"#38393b\",\n        \"base.yellow\": \"#FEB454\",\n        \"base.red\": \"#ef7177\",\n        \"base.blue\": \"#5ac1fe\",\n        \"base.green\": \"#aad84c\",\n        \"base.magenta\": \"#d2a6ff\",\n        \"base.cyan\": \"#5a728b\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#bfbdb6\",\n        \"editor.background\": \"#0d1016\",\n        \"editor.active_line.background\": \"#1f2127bf\",\n        \"editor.line_number\": \"#4b4c4e\",\n        \"editor.active_line_number\": \"#DDDDDD\",\n        \"editor.invisible\": \"#73777b66\",\n        \"conflict\": \"#feb454ff\",\n        \"conflict.background\": \"#572815ff\",\n        \"conflict.border\": \"#754221ff\",\n        \"created\": \"#aad84cff\",\n        \"created.background\": \"#294113ff\",\n        \"created.border\": \"#405c1cff\",\n        \"deleted\": \"#ef7177ff\",\n        \"deleted.background\": \"#48161bff\",\n        \"deleted.border\": \"#66272dff\",\n        \"error\": \"#ef7177ff\",\n        \"error.background\": \"#48161bff\",\n        \"error.border\": \"#66272dff\",\n        \"hidden\": \"#696a6aff\",\n        \"hidden.background\": \"#313337ff\",\n        \"hidden.border\": \"#383a3eff\",\n        \"hint\": \"#628b80ff\",\n        \"hint.background\": \"#0d2f4eff\",\n        \"hint.border\": \"#1b4a6eff\",\n        \"ignored\": \"#696a6aff\",\n        \"ignored.background\": \"#313337ff\",\n        \"ignored.border\": \"#3f4043ff\",\n        \"info\": \"#5ac1feff\",\n        \"info.background\": \"#0d2f4eff\",\n        \"info.border\": \"#1b4a6eff\",\n        \"modified\": \"#feb454ff\",\n        \"modified.background\": \"#572815ff\",\n        \"modified.border\": \"#754221ff\",\n        \"predictive\": \"#5a728bff\",\n        \"predictive.background\": \"#294113ff\",\n        \"predictive.border\": \"#405c1cff\",\n        \"renamed\": \"#5ac1feff\",\n        \"renamed.background\": \"#0d2f4eff\",\n        \"renamed.border\": \"#1b4a6eff\",\n        \"success\": \"#aad84cff\",\n        \"success.background\": \"#294113ff\",\n        \"success.border\": \"#405c1cff\",\n        \"unreachable\": \"#8a8986ff\",\n        \"unreachable.background\": \"#313337ff\",\n        \"unreachable.border\": \"#3f4043ff\",\n        \"warning\": \"#feb454ff\",\n        \"warning.background\": \"#572815ff\",\n        \"warning.border\": \"#754221ff\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#5ac1feff\"\n          },\n          \"boolean\": {\n            \"color\": \"#d2a6ffff\"\n          },\n          \"comment\": {\n            \"color\": \"#8c8b88ff\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#8c8b88ff\"\n          },\n          \"constant\": {\n            \"color\": \"#d2a6ffff\"\n          },\n          \"constructor\": {\n            \"color\": \"#5ac1feff\"\n          },\n          \"embedded\": {\n            \"color\": \"#bfbdb6ff\"\n          },\n          \"emphasis\": {\n            \"color\": \"#5ac1feff\"\n          },\n          \"emphasis.strong\": {\n            \"color\": \"#5ac1feff\",\n            \"font_weight\": 700\n          },\n          \"enum\": {\n            \"color\": \"#fe8f40ff\"\n          },\n          \"function\": {\n            \"color\": \"#ffb353ff\"\n          },\n          \"hint\": {\n            \"color\": \"#628b80ff\",\n            \"font_weight\": 700\n          },\n          \"keyword\": {\n            \"color\": \"#ff8f3fff\"\n          },\n          \"label\": {\n            \"color\": \"#5ac1feff\"\n          },\n          \"link_text\": {\n            \"color\": \"#fe8f40ff\",\n            \"font_style\": \"italic\"\n          },\n          \"link_uri\": {\n            \"color\": \"#aad84cff\"\n          },\n          \"namespace\": {\n            \"color\": \"#bfbdb6ff\"\n          },\n          \"number\": {\n            \"color\": \"#d2a6ffff\"\n          },\n          \"operator\": {\n            \"color\": \"#f29668ff\"\n          },\n          \"predictive\": {\n            \"color\": \"#5a728bff\",\n            \"font_style\": \"italic\"\n          },\n          \"preproc\": {\n            \"color\": \"#bfbdb6ff\"\n          },\n          \"primary\": {\n            \"color\": \"#bfbdb6ff\"\n          },\n          \"property\": {\n            \"color\": \"#5ac1feff\"\n          },\n          \"punctuation\": {\n            \"color\": \"#a6a5a0ff\"\n          },\n          \"punctuation.bracket\": {\n            \"color\": \"#a6a5a0ff\"\n          },\n          \"punctuation.delimiter\": {\n            \"color\": \"#a6a5a0ff\"\n          },\n          \"punctuation.list_marker\": {\n            \"color\": \"#a6a5a0ff\"\n          },\n          \"punctuation.markup\": {\n            \"color\": \"#a6a5a0ff\"\n          },\n          \"punctuation.special\": {\n            \"color\": \"#d2a6ffff\"\n          },\n          \"selector\": {\n            \"color\": \"#d2a6ffff\"\n          },\n          \"selector.pseudo\": {\n            \"color\": \"#5ac1feff\"\n          },\n          \"string\": {\n            \"color\": \"#a9d94bff\"\n          },\n          \"string.escape\": {\n            \"color\": \"#8c8b88ff\"\n          },\n          \"string.regex\": {\n            \"color\": \"#95e6cbff\"\n          },\n          \"string.special\": {\n            \"color\": \"#e5b572ff\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#fe8f40ff\"\n          },\n          \"tag\": {\n            \"color\": \"#5ac1feff\"\n          },\n          \"text.literal\": {\n            \"color\": \"#fe8f40ff\"\n          },\n          \"title\": {\n            \"color\": \"#bfbdb6ff\",\n            \"font_weight\": 700\n          },\n          \"type\": {\n            \"color\": \"#59c2ffff\"\n          },\n          \"variable\": {\n            \"color\": \"#bfbdb6ff\"\n          },\n          \"variant\": {\n            \"color\": \"#5ac1feff\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/catppuccin.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Catppuccin\",\n  \"author\": \"Catppuccino\",\n  \"url\": \"https://github.com/catppuccin/catppuccin\",\n  \"themes\": [\n    {\n      \"name\": \"Catppuccin Latte\",\n      \"mode\": \"light\",\n      \"colors\": {\n        \"accent.background\": \"#d3d8e0\",\n        \"accent.foreground\": \"#4c4f69\",\n        \"background\": \"#E5E9EF\",\n        \"border\": \"#CCD0DA\",\n        \"ring\": \"#7287fd\",\n        \"foreground\": \"#4c4f69\",\n        \"input.border\": \"#acb0be\",\n        \"link.active.foreground\": \"#7287fd\",\n        \"link.foreground\": \"#7287fd\",\n        \"link.hover.foreground\": \"#7287fd\",\n        \"list.active.background\": \"#7287fd22\",\n        \"list.active.border\": \"#7287fd\",\n        \"list.even.background\": \"#EFF1F5\",\n        \"list.head.background\": \"#dce0e8\",\n        \"muted.background\": \"#dce0e8\",\n        \"muted.foreground\": \"#9a9db2\",\n        \"panel.background\": \"#dce0e8\",\n        \"primary.active.background\": \"#7287fd\",\n        \"primary.background\": \"#7287fd\",\n        \"primary.foreground\": \"#EFF1F5\",\n        \"scrollbar.background\": \"#EFF1F500\",\n        \"scrollbar.thumb.background\": \"#acb0be\",\n        \"secondary.active.background\": \"#CCD2DE\",\n        \"secondary.background\": \"#dce0e8\",\n        \"secondary.foreground\": \"#4c4f69\",\n        \"secondary.hover.background\": \"#CCD2DE99\",\n        \"tab.active.background\": \"#E5E9EF\",\n        \"tab.active.foreground\": \"#4c4f69\",\n        \"tab.background\": \"#D2D7E200\",\n        \"tab.foreground\": \"#82848c\",\n        \"tab_bar.background\": \"#DCE0E8\",\n        \"title_bar.background\": \"#DCE0E8\",\n        \"title_bar.border\": \"#bec3d0\",\n        \"base.red\": \"#d26a53\",\n        \"base.green\": \"#5aa93b\",\n        \"base.yellow\": \"#df8e1d\",\n        \"base.blue\": \"#78acdc\",\n        \"base.magenta\": \"#8778dc\",\n        \"base.magenta.light\": \"#9978dc66\",\n        \"base.cyan\": \"#53d2b0\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#4c4f69\",\n        \"editor.background\": \"#EFF1F5\",\n        \"editor.active_line.background\": \"#dce0e8\",\n        \"editor.line_number\": \"#9a9db2\",\n        \"editor.active_line_number\": \"#4c4f69\",\n        \"editor.invisible\": \"#7c7f9366\",\n        \"conflict\": \"#d20f39\",\n        \"created\": \"#85dc78\",\n        \"deleted\": \"#dc8a78\",\n        \"error\": \"#d20f39\",\n        \"hidden\": \"#9a9db2\",\n        \"hint\": \"#7287fd\",\n        \"ignored\": \"#acb0be\",\n        \"info\": \"#1e66f5\",\n        \"modified\": \"#df8e1d\",\n        \"predictive\": \"#9a9db2\",\n        \"renamed\": \"#9978dc\",\n        \"success\": \"#85dc78\",\n        \"unreachable\": \"#acb0be\",\n        \"warning\": \"#df8e1d\",\n        \"syntax\": {\n          \"variable\": {\n            \"color\": \"#4c4f69\"\n          },\n          \"variable.builtin\": {\n            \"color\": \"#d20f39\"\n          },\n          \"variable.parameter\": {\n            \"color\": \"#e64553\"\n          },\n          \"variable.member\": {\n            \"color\": \"#1e66f5\"\n          },\n          \"variable.special\": {\n            \"color\": \"#d20f39\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#fe640b\"\n          },\n          \"constant.builtin\": {\n            \"color\": \"#fe640b\"\n          },\n          \"constant.macro\": {\n            \"color\": \"#8839ef\"\n          },\n          \"module\": {\n            \"color\": \"#df8e1d\",\n            \"font_style\": \"italic\"\n          },\n          \"label\": {\n            \"color\": \"#209fb5\"\n          },\n          \"string\": {\n            \"color\": \"#85dc78\"\n          },\n          \"string.documentation\": {\n            \"color\": \"#179299\"\n          },\n          \"string.regexp\": {\n            \"color\": \"#fe640b\"\n          },\n          \"string.escape\": {\n            \"color\": \"#ea76cb\"\n          },\n          \"string.special\": {\n            \"color\": \"#ea76cb\"\n          },\n          \"string.special.path\": {\n            \"color\": \"#ea76cb\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#dd7878\"\n          },\n          \"string.special.url\": {\n            \"color\": \"#dc8a78\",\n            \"font_style\": \"italic\"\n          },\n          \"character\": {\n            \"color\": \"#179299\"\n          },\n          \"character.special\": {\n            \"color\": \"#ea76cb\"\n          },\n          \"boolean\": {\n            \"color\": \"#fe640b\"\n          },\n          \"number\": {\n            \"color\": \"#fe640b\"\n          },\n          \"number.float\": {\n            \"color\": \"#fe640b\"\n          },\n          \"type\": {\n            \"color\": \"#df8e1d\"\n          },\n          \"type.builtin\": {\n            \"color\": \"#8839ef\",\n            \"font_style\": \"italic\"\n          },\n          \"type.definition\": {\n            \"color\": \"#df8e1d\"\n          },\n          \"type.interface\": {\n            \"color\": \"#df8e1d\",\n            \"font_style\": \"italic\"\n          },\n          \"type.super\": {\n            \"color\": \"#df8e1d\",\n            \"font_style\": \"italic\"\n          },\n          \"attribute\": {\n            \"color\": \"#fe640b\"\n          },\n          \"property\": {\n            \"color\": \"#1e66f5\"\n          },\n          \"function\": {\n            \"color\": \"#1e66f5\"\n          },\n          \"function.builtin\": {\n            \"color\": \"#fe640b\"\n          },\n          \"function.call\": {\n            \"color\": \"#1e66f5\"\n          },\n          \"function.macro\": {\n            \"color\": \"#179299\"\n          },\n          \"function.method\": {\n            \"color\": \"#1e66f5\"\n          },\n          \"function.method.call\": {\n            \"color\": \"#1e66f5\"\n          },\n          \"constructor\": {\n            \"color\": \"#dd7878\"\n          },\n          \"operator\": {\n            \"color\": \"#04a5e5\"\n          },\n          \"keyword\": {\n            \"color\": \"#8839ef\"\n          },\n          \"keyword.modifier\": {\n            \"color\": \"#8839ef\"\n          },\n          \"keyword.type\": {\n            \"color\": \"#8839ef\"\n          },\n          \"keyword.coroutine\": {\n            \"color\": \"#8839ef\"\n          },\n          \"keyword.function\": {\n            \"color\": \"#8839ef\"\n          },\n          \"keyword.operator\": {\n            \"color\": \"#8839ef\"\n          },\n          \"keyword.import\": {\n            \"color\": \"#8839ef\"\n          },\n          \"keyword.repeat\": {\n            \"color\": \"#8839ef\"\n          },\n          \"keyword.return\": {\n            \"color\": \"#8839ef\"\n          },\n          \"keyword.debug\": {\n            \"color\": \"#8839ef\"\n          },\n          \"keyword.exception\": {\n            \"color\": \"#8839ef\"\n          },\n          \"keyword.conditional\": {\n            \"color\": \"#8839ef\"\n          },\n          \"keyword.conditional.ternary\": {\n            \"color\": \"#8839ef\"\n          },\n          \"keyword.directive\": {\n            \"color\": \"#ea76cb\"\n          },\n          \"keyword.directive.define\": {\n            \"color\": \"#ea76cb\"\n          },\n          \"keyword.export\": {\n            \"color\": \"#04a5e5\"\n          },\n          \"punctuation\": {\n            \"color\": \"#7c7f93\"\n          },\n          \"punctuation.delimiter\": {\n            \"color\": \"#7c7f93\"\n          },\n          \"punctuation.bracket\": {\n            \"color\": \"#7c7f93\"\n          },\n          \"punctuation.special\": {\n            \"color\": \"#ea76cb\"\n          },\n          \"punctuation.special.symbol\": {\n            \"color\": \"#dd7878\"\n          },\n          \"punctuation.list_marker\": {\n            \"color\": \"#179299\"\n          },\n          \"comment\": {\n            \"color\": \"#7c7f93\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#7c7f93\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.documentation\": {\n            \"color\": \"#7c7f93\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.error\": {\n            \"color\": \"#d20f39\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.warning\": {\n            \"color\": \"#df8e1d\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.hint\": {\n            \"color\": \"#1e66f5\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.todo\": {\n            \"color\": \"#dd7878\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.note\": {\n            \"color\": \"#dc8a78\",\n            \"font_style\": \"italic\"\n          },\n          \"diff.plus\": {\n            \"color\": \"#40a02b\"\n          },\n          \"diff.minus\": {\n            \"color\": \"#d20f39\"\n          },\n          \"tag\": {\n            \"color\": \"#1e66f5\"\n          },\n          \"tag.attribute\": {\n            \"color\": \"#df8e1d\",\n            \"font_style\": \"italic\"\n          },\n          \"tag.delimiter\": {\n            \"color\": \"#179299\"\n          },\n          \"parameter\": {\n            \"color\": \"#e64553\"\n          },\n          \"field\": {\n            \"color\": \"#7287fd\"\n          },\n          \"namespace\": {\n            \"color\": \"#df8e1d\",\n            \"font_style\": \"italic\"\n          },\n          \"float\": {\n            \"color\": \"#fe640b\"\n          },\n          \"symbol\": {\n            \"color\": \"#ea76cb\"\n          },\n          \"string.regex\": {\n            \"color\": \"#fe640b\"\n          },\n          \"text\": {\n            \"color\": \"#4c4f69\"\n          },\n          \"emphasis.strong\": {\n            \"color\": \"#e64553\",\n            \"font_weight\": 700\n          },\n          \"emphasis\": {\n            \"color\": \"#e64553\",\n            \"font_style\": \"italic\"\n          },\n          \"embedded\": {\n            \"color\": \"#e64553\"\n          },\n          \"text.literal\": {\n            \"color\": \"#40a02b\"\n          },\n          \"concept\": {\n            \"color\": \"#209fb5\"\n          },\n          \"enum\": {\n            \"color\": \"#179299\",\n            \"font_weight\": 700\n          },\n          \"function.decorator\": {\n            \"color\": \"#fe640b\"\n          },\n          \"type.class.definition\": {\n            \"color\": \"#df8e1d\",\n            \"font_weight\": 700\n          },\n          \"hint\": {\n            \"color\": \"#acb0be\",\n            \"font_style\": \"italic\"\n          },\n          \"link_text\": {\n            \"color\": \"#7287fd\"\n          },\n          \"link_uri\": {\n            \"color\": \"#1e66f5\",\n            \"font_style\": \"italic\"\n          },\n          \"parent\": {\n            \"color\": \"#fe640b\"\n          },\n          \"predictive\": {\n            \"color\": \"#9ca0b0\"\n          },\n          \"predoc\": {\n            \"color\": \"#d20f39\"\n          },\n          \"primary\": {\n            \"color\": \"#e64553\"\n          },\n          \"tag.doctype\": {\n            \"color\": \"#8839ef\"\n          },\n          \"string.doc\": {\n            \"color\": \"#179299\",\n            \"font_style\": \"italic\"\n          },\n          \"title\": {\n            \"color\": \"#4c4f69\",\n            \"font_weight\": 800\n          },\n          \"variant\": {\n            \"color\": \"#d20f39\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Catppuccin Frappe\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#3b3f4f\",\n        \"accent.foreground\": \"#c6d0f5\",\n        \"background\": \"#232634\",\n        \"border\": \"#3e4255\",\n        \"ring\": \"#f2d5cf\",\n        \"foreground\": \"#c6d0f5\",\n        \"input.border\": \"#51576d\",\n        \"list.active.background\": \"#8caaee15\",\n        \"list.even.background\": \"#303446\",\n        \"list.head.background\": \"#2A2C3C\",\n        \"muted.background\": \"#2c303f\",\n        \"muted.foreground\": \"#979db5\",\n        \"panel.background\": \"#414559\",\n        \"popover.background\": \"#1E202B\",\n        \"popover.foreground\": \"#c6d0f5\",\n        \"primary.active.background\": \"#8caaee\",\n        \"primary.background\": \"#8caaee\",\n        \"primary.foreground\": \"#232634\",\n        \"primary.hover.background\": \"#8caaee\",\n        \"scrollbar.background\": \"#30344600\",\n        \"scrollbar.thumb.background\": \"#51576d\",\n        \"secondary.active.background\": \"#313445\",\n        \"secondary.background\": \"#414559\",\n        \"secondary.foreground\": \"#c6d0f5\",\n        \"secondary.hover.background\": \"#31344599\",\n        \"tab.active.background\": \"#232634\",\n        \"tab.active.foreground\": \"#dce2f9\",\n        \"tab.background\": \"#1E202B00\",\n        \"tab.foreground\": \"#abb5d9\",\n        \"tab_bar.background\": \"#1E202B\",\n        \"title_bar.background\": \"#1D202B\",\n        \"title_bar.border\": \"#30354b\",\n        \"base.red\": \"#e78284\",\n        \"base.green\": \"#a6d189\",\n        \"base.yellow\": \"#e7d682\",\n        \"base.blue\": \"#8caaee\",\n        \"base.magenta\": \"#9882e7\",\n        \"base.magenta.light\": \"#9882e766\",\n        \"base.cyan\": \"#81c8be\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#c6d0f5\",\n        \"editor.background\": \"#232634\",\n        \"editor.active_line.background\": \"#41455977\",\n        \"editor.line_number\": \"#979db5\",\n        \"editor.active_line_number\": \"#c6d0f5\",\n        \"editor.invisible\": \"#7c7f9366\",\n        \"conflict\": \"#e78284\",\n        \"created\": \"#a6d189\",\n        \"deleted\": \"#3b2e2e\",\n        \"error\": \"#3b2e2e\",\n        \"hidden\": \"#51576d\",\n        \"hint\": \"#8caaee\",\n        \"info\": \"#1e2b4d\",\n        \"modified\": \"#e7d682\",\n        \"predictive\": \"#51576d\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#e7d682\"\n          },\n          \"boolean\": {\n            \"color\": \"#e78284\"\n          },\n          \"comment\": {\n            \"color\": \"#51576d\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#51576d\"\n          },\n          \"constant\": {\n            \"color\": \"#e7d682\"\n          },\n          \"constructor\": {\n            \"color\": \"#a6d189\"\n          },\n          \"embedded\": {\n            \"color\": \"#c6d0f5\"\n          },\n          \"function\": {\n            \"color\": \"#8caaee\"\n          },\n          \"keyword\": {\n            \"color\": \"#e78284\"\n          },\n          \"link_text\": {\n            \"color\": \"#8caaee\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#51576d\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#e78284\"\n          },\n          \"string\": {\n            \"color\": \"#a6d189\"\n          },\n          \"string.escape\": {\n            \"color\": \"#a6d189\"\n          },\n          \"string.regex\": {\n            \"color\": \"#a6d189\"\n          },\n          \"string.special\": {\n            \"color\": \"#e7d682\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#e7d682\"\n          },\n          \"tag\": {\n            \"color\": \"#8caaee\"\n          },\n          \"text.literal\": {\n            \"color\": \"#c6d0f5\"\n          },\n          \"title\": {\n            \"color\": \"#e78284\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#8caaee\"\n          },\n          \"property\": {\n            \"color\": \"#c6d0f5\"\n          },\n          \"variable.special\": {\n            \"color\": \"#e78284\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Catppuccin Macchiato\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#30344d\",\n        \"accent.foreground\": \"#cad3f5\",\n        \"action_bar.background\": \"#1E2030\",\n        \"background\": \"#1E2030\",\n        \"border\": \"#494d64\",\n        \"ring\": \"#f4dbd6\",\n        \"danger.background\": \"#ed8796\",\n        \"foreground\": \"#cad3f5\",\n        \"input.border\": \"#494d64\",\n        \"link.active.foreground\": \"#8aadf4\",\n        \"link.foreground\": \"#8aadf4\",\n        \"link.hover.foreground\": \"#8aadf4\",\n        \"list.active.background\": \"#8aadf420\",\n        \"list.active.border\": \"#8aadf4\",\n        \"list.even.background\": \"#24273a\",\n        \"list.head.background\": \"#363a4f33\",\n        \"muted.background\": \"#282a3a\",\n        \"muted.foreground\": \"#b8c0e0\",\n        \"popover.foreground\": \"#cad3f5\",\n        \"primary.active.background\": \"#8aadf499\",\n        \"primary.background\": \"#8aadf4\",\n        \"primary.foreground\": \"#24273a\",\n        \"primary.hover.background\": \"#8aadf4\",\n        \"scrollbar.background\": \"#24273a00\",\n        \"scrollbar.thumb.background\": \"#494d64\",\n        \"secondary.active.background\": \"#3a3f55\",\n        \"secondary.background\": \"#363a4f\",\n        \"secondary.foreground\": \"#cad3f5\",\n        \"secondary.hover.background\": \"#24273a99\",\n        \"tab.background\": \"#17192600\",\n        \"tab.foreground\": \"#aeb8db\",\n        \"tab_bar.background\": \"#171926\",\n        \"title_bar.background\": \"#171926\",\n        \"title_bar.border\": \"#363A4F\",\n        \"base.red\": \"#ed8796\",\n        \"base.green\": \"#a6da95\",\n        \"base.yellow\": \"#eed49f\",\n        \"base.blue\": \"#8aadf4\",\n        \"base.magenta\": \"#f5bde6\",\n        \"base.magenta.light\": \"#f5bde666\",\n        \"base.cyan\": \"#8bd5ca\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#cad3f5\",\n        \"editor.background\": \"#1E2030\",\n        \"editor.active_line.background\": \"#363a4f\",\n        \"editor.line_number\": \"#b8c0e0\",\n        \"editor.active_line_number\": \"#cad3f5\",\n        \"editor.invisible\": \"#7c7f9366\",\n        \"conflict\": \"#ed8796\",\n        \"created\": \"#a6da95\",\n        \"deleted\": \"#ed8796\",\n        \"error\": \"#ed8796\",\n        \"hidden\": \"#b8c0e0\",\n        \"hint\": \"#8aadf4\",\n        \"ignored\": \"#494d64\",\n        \"info\": \"#8aadf4\",\n        \"modified\": \"#eed49f\",\n        \"predictive\": \"#b8c0e0\",\n        \"renamed\": \"#f5bde6\",\n        \"success\": \"#a6da95\",\n        \"unreachable\": \"#b8c0e0\",\n        \"warning\": \"#eed49f\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#eed49f\"\n          },\n          \"boolean\": {\n            \"color\": \"#f5bde6\"\n          },\n          \"comment\": {\n            \"color\": \"#b8c0e0\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#b8c0e0\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#eed49f\"\n          },\n          \"constructor\": {\n            \"color\": \"#8aadf4\"\n          },\n          \"embedded\": {\n            \"color\": \"#cad3f5\"\n          },\n          \"function\": {\n            \"color\": \"#8aadf4\"\n          },\n          \"keyword\": {\n            \"color\": \"#f5bde6\"\n          },\n          \"link_text\": {\n            \"color\": \"#8aadf4\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#b8c0e0\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#eed49f\"\n          },\n          \"string\": {\n            \"color\": \"#a6da95\"\n          },\n          \"string.escape\": {\n            \"color\": \"#a6da95\"\n          },\n          \"string.regex\": {\n            \"color\": \"#a6da95\"\n          },\n          \"string.special\": {\n            \"color\": \"#eed49f\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#eed49f\"\n          },\n          \"tag\": {\n            \"color\": \"#8aadf4\"\n          },\n          \"text.literal\": {\n            \"color\": \"#cad3f5\"\n          },\n          \"title\": {\n            \"color\": \"#f5bde6\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#8aadf4\"\n          },\n          \"property\": {\n            \"color\": \"#cad3f5\"\n          },\n          \"variable.special\": {\n            \"color\": \"#f5bde6\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Catppuccin Mocha\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#2e2e3e\",\n        \"accent.foreground\": \"#cdd6f4\",\n        \"background\": \"#181825\",\n        \"border\": \"#313244\",\n        \"ring\": \"#cba6f7\",\n        \"danger.background\": \"#f38ba8\",\n        \"danger.active.background\": \"#eba0ac\",\n        \"danger.hover.background\": \"#f38ba8\",\n        \"foreground\": \"#cdd6f4\",\n        \"input.border\": \"#6c7086\",\n        \"link.active.foreground\": \"#89b4fa\",\n        \"link.foreground\": \"#89b4fa\",\n        \"link.hover.foreground\": \"#89b4fa\",\n        \"list.active.background\": \"#89b4fa15\",\n        \"list.active.border\": \"#89b4fa77\",\n        \"list.hover.background\": \"#202031\",\n        \"list.even.background\": \"#161622\",\n        \"list.head.background\": \"#1E1E2E\",\n        \"muted.background\": \"#302d41\",\n        \"muted.foreground\": \"#6c7086\",\n        \"panel.background\": \"#302d41\",\n        \"popover.background\": \"#1e1e2e\",\n        \"popover.foreground\": \"#cdd6f4\",\n        \"primary.active.background\": \"#89b4fa\",\n        \"primary.background\": \"#89b4fa\",\n        \"primary.foreground\": \"#1e1e2e\",\n        \"primary.hover.background\": \"#74c7ec\",\n        \"scrollbar.background\": \"#1e1e2e00\",\n        \"scrollbar.thumb.background\": \"#4e4e5e\",\n        \"secondary.active.background\": \"#45475a\",\n        \"secondary.background\": \"#302d41\",\n        \"secondary.foreground\": \"#cdd6f4\",\n        \"secondary.hover.background\": \"#575268\",\n        \"tab.background\": \"#0B0B1100\",\n        \"tab.foreground\": \"#aeb7d5\",\n        \"tab_bar.background\": \"#0B0B11\",\n        \"title_bar.background\": \"#11111B\",\n        \"title_bar.border\": \"#313244\",\n        \"base.red\": \"#f38ba8\",\n        \"base.green\": \"#a6e3a1\",\n        \"base.yellow\": \"#f9e2af\",\n        \"base.blue\": \"#89b4fa\",\n        \"base.magenta\": \"#f5c2e7\",\n        \"base.magenta.light\": \"#f38ba866\",\n        \"base.cyan\": \"#94e2d5\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#cdd6f4\",\n        \"editor.background\": \"#181825\",\n        \"editor.active_line.background\": \"#222230AA\",\n        \"editor.line_number\": \"#6c7086\",\n        \"editor.active_line_number\": \"#cdd6f4\",\n        \"editor.invisible\": \"#7c7f9366\",\n        \"conflict\": \"#f38ba8\",\n        \"created\": \"#a6e3a1\",\n        \"deleted\": \"#f38ba8\",\n        \"error\": \"#f38ba8\",\n        \"hidden\": \"#6c7086\",\n        \"hint\": \"#89b4fa\",\n        \"ignored\": \"#6c7086\",\n        \"info\": \"#89b4fa\",\n        \"modified\": \"#f9e2af\",\n        \"predictive\": \"#6c7086\",\n        \"renamed\": \"#f5c2e7\",\n        \"success\": \"#a6e3a1\",\n        \"unreachable\": \"#6c7086\",\n        \"warning\": \"#f9e2af\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#f9e2af\"\n          },\n          \"boolean\": {\n            \"color\": \"#f5c2e7\"\n          },\n          \"comment\": {\n            \"color\": \"#6c7086\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#6c7086\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#f9e2af\"\n          },\n          \"constructor\": {\n            \"color\": \"#89b4fa\"\n          },\n          \"embedded\": {\n            \"color\": \"#cdd6f4\"\n          },\n          \"function\": {\n            \"color\": \"#89b4fa\"\n          },\n          \"keyword\": {\n            \"color\": \"#f5c2e7\"\n          },\n          \"link_text\": {\n            \"color\": \"#89b4fa\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#6c7086\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#f9e2af\"\n          },\n          \"string\": {\n            \"color\": \"#a6e3a1\"\n          },\n          \"string.escape\": {\n            \"color\": \"#a6e3a1\"\n          },\n          \"string.regex\": {\n            \"color\": \"#a6e3a1\"\n          },\n          \"string.special\": {\n            \"color\": \"#f9e2af\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#f9e2af\"\n          },\n          \"tag\": {\n            \"color\": \"#89b4fa\"\n          },\n          \"text.literal\": {\n            \"color\": \"#cdd6f4\"\n          },\n          \"title\": {\n            \"color\": \"#f5c2e7\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#89b4fa\"\n          },\n          \"property\": {\n            \"color\": \"#cdd6f4\"\n          },\n          \"variable.special\": {\n            \"color\": \"#f5c2e7\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/everforest.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Everforest\",\n  \"author\": \"sainnhe\",\n  \"url\": \"https://github.com/sainnhe/everforest\",\n  \"themes\": [\n    {\n      \"name\": \"Everforest Light\",\n      \"mode\": \"light\",\n      \"colors\": {\n        \"accent.background\": \"#E7E5D4\",\n        \"accent.foreground\": \"#5F6D75\",\n        \"background\": \"#FEFCEE\",\n        \"border\": \"#E7E5D4\",\n        \"danger.background\": \"#e67e80\",\n        \"danger.foreground\": \"#ffffff\",\n        \"foreground\": \"#5F6D75\",\n        \"input.border\": \"#E7E5D4\",\n        \"link.active.foreground\": \"#7fbbb3\",\n        \"link.foreground\": \"#7fbbb3\",\n        \"link.hover.foreground\": \"#7fbbb3\",\n        \"list.active.background\": \"#a7c08025\",\n        \"list.active.border\": \"#a7c080\",\n        \"list.even.background\": \"#F4F1E2\",\n        \"list.head.background\": \"#F4F1E2\",\n        \"muted.background\": \"#f1f0e2\",\n        \"muted.foreground\": \"#959a9d\",\n        \"popover.background\": \"#FEFCEE\",\n        \"popover.foreground\": \"#5F6D75\",\n        \"primary.background\": \"#e69875\",\n        \"primary.foreground\": \"#FEFCEE\",\n        \"scrollbar.background\": \"#e0e0e000\",\n        \"scrollbar.thumb.background\": \"#D7D5C4\",\n        \"secondary.background\": \"#EEEADA\",\n        \"secondary.foreground\": \"#5F6D75\",\n        \"tab.active.background\": \"#FEFCEE\",\n        \"tab.active.foreground\": \"#5F6D75\",\n        \"tab.background\": \"#F4F1E200\",\n        \"tab.foreground\": \"#6b7b84\",\n        \"tab_bar.background\": \"#F4F1E2\",\n        \"title_bar.background\": \"#F9F5E4\",\n        \"title_bar.border\": \"#E7E5D4\",\n        \"base.red\": \"#e67e80\",\n        \"base.green\": \"#a7c080\",\n        \"base.yellow\": \"#dbbc7f\",\n        \"base.blue\": \"#7fbbb3\",\n        \"base.magenta\": \"#d699b6\",\n        \"base.magenta.light\": \"#d699b699\",\n        \"base.cyan\": \"#83c092\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#5F6D75\",\n        \"editor.background\": \"#FEFCEE\",\n        \"editor.active_line.background\": \"#E7E5D4\",\n        \"editor.line_number\": \"#959a9d\",\n        \"editor.active_line_number\": \"#5F6D75\",\n        \"editor.invisible\": \"#959a9d66\",\n        \"conflict\": \"#e67e80\",\n        \"created\": \"#a7c080\",\n        \"deleted\": \"#e67e80\",\n        \"error\": \"#e67e80\",\n        \"hidden\": \"#959a9d\",\n        \"hint\": \"#7fbbb3\",\n        \"ignored\": \"#959a9d\",\n        \"info\": \"#7fbbb3\",\n        \"modified\": \"#dbbc7f\",\n        \"predictive\": \"#5F6D75\",\n        \"renamed\": \"#a7c080\",\n        \"success\": \"#a7c080\",\n        \"unreachable\": \"#959a9d\",\n        \"warning\": \"#dbbc7f\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#dbbc7f\"\n          },\n          \"boolean\": {\n            \"color\": \"#e69875\"\n          },\n          \"comment\": {\n            \"color\": \"#959a9d\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#959a9d\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#e69875\"\n          },\n          \"constructor\": {\n            \"color\": \"#a7c080\"\n          },\n          \"embedded\": {\n            \"color\": \"#5F6D75\"\n          },\n          \"function\": {\n            \"color\": \"#a7c080\"\n          },\n          \"keyword\": {\n            \"color\": \"#e67e80\"\n          },\n          \"link_text\": {\n            \"color\": \"#7fbbb3\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#5F6D75\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#e69875\"\n          },\n          \"string\": {\n            \"color\": \"#a7c080\"\n          },\n          \"string.escape\": {\n            \"color\": \"#a7c080\"\n          },\n          \"string.regex\": {\n            \"color\": \"#a7c080\"\n          },\n          \"string.special\": {\n            \"color\": \"#dbbc7f\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#dbbc7f\"\n          },\n          \"tag\": {\n            \"color\": \"#a7c080\"\n          },\n          \"text.literal\": {\n            \"color\": \"#dbbc7f\"\n          },\n          \"title\": {\n            \"color\": \"#e69875\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#7fbbb3\"\n          },\n          \"property\": {\n            \"color\": \"#5F6D75\"\n          },\n          \"variable.special\": {\n            \"color\": \"#e67e80\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Everforest Dark\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#3c4448\",\n        \"accent.foreground\": \"#d3c6aa\",\n        \"background\": \"#262E34\",\n        \"border\": \"#40484c\",\n        \"foreground\": \"#d3c6aa\",\n        \"input.border\": \"#485156\",\n        \"link.active.foreground\": \"#7fbbb3\",\n        \"link.foreground\": \"#7fbbb3\",\n        \"link.hover.foreground\": \"#7fbbb3\",\n        \"list.active.background\": \"#a7c08022\",\n        \"list.active.border\": \"#a7c08088\",\n        \"list.even.background\": \"#2E383B\",\n        \"list.head.background\": \"#2E383B\",\n        \"muted.background\": \"#2E383B\",\n        \"muted.foreground\": \"#6D7873\",\n        \"panel.background\": \"#2E383B\",\n        \"popover.background\": \"#262E34\",\n        \"popover.foreground\": \"#d3c6aa\",\n        \"primary.background\": \"#e69875\",\n        \"primary.foreground\": \"#262E34\",\n        \"scrollbar.background\": \"#2E383B00\",\n        \"scrollbar.thumb.background\": \"#485156\",\n        \"secondary.background\": \"#2E383B\",\n        \"secondary.foreground\": \"#849087\",\n        \"secondary.active.background\": \"#303b3e\",\n        \"secondary.hover.background\": \"#3E474B99\",\n        \"tab.active.background\": \"#262E34\",\n        \"tab.active.foreground\": \"#d3c6aa\",\n        \"tab.background\": \"#262E3400\",\n        \"tab.foreground\": \"#849087\",\n        \"tab_bar.background\": \"#2E383B\",\n        \"title_bar.background\": \"#1f262b\",\n        \"title_bar.border\": \"#40484c\",\n        \"base.red\": \"#e67e80\",\n        \"base.green\": \"#a7c080\",\n        \"base.yellow\": \"#dbbc7f\",\n        \"base.blue\": \"#7fbbb3\",\n        \"base.magenta\": \"#844d64\",\n        \"base.magenta.light\": \"#844d6499\",\n        \"base.cyan\": \"#83c092\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#d3c6aa\",\n        \"editor.background\": \"#262E34\",\n        \"editor.active_line.background\": \"#3c4448\",\n        \"editor.line_number\": \"#8F8F8F\",\n        \"editor.active_line_number\": \"#DDDDDD\",\n        \"editor.invisible\": \"#959a9d66\",\n        \"conflict\": \"#D2602D\",\n        \"created\": \"#3f72e2\",\n        \"hidden\": \"#9E9E9E\",\n        \"hint\": \"#b283f8\",\n        \"modified\": \"#B0A878\",\n        \"predictive\": \"#5D5945\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#be9a52\"\n          },\n          \"boolean\": {\n            \"color\": \"#E1D797\"\n          },\n          \"comment\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"constant\": {\n            \"color\": \"#E1D797\"\n          },\n          \"constructor\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"embedded\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"function\": {\n            \"color\": \"#E1D797\"\n          },\n          \"keyword\": {\n            \"color\": \"#E19773\"\n          },\n          \"link_text\": {\n            \"color\": \"#A86D3B\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#6F6D66\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#E19773\"\n          },\n          \"string\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.escape\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.regex\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.special\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#E1D797\"\n          },\n          \"tag\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"text.literal\": {\n            \"color\": \"#E1D797\"\n          },\n          \"title\": {\n            \"color\": \"#A76D3B\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#A86D3B\"\n          },\n          \"property\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"variable.special\": {\n            \"color\": \"#E19773\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/fahrenheit.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Fahrenheit\",\n  \"author\": \"iTerm2-Color-Schemes\",\n  \"url\": \"https://github.com/mbadolato/iTerm2-Color-Schemes\",\n  \"themes\": [\n    {\n      \"name\": \"Fahrenheit\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#171717\",\n        \"accent.foreground\": \"#FFFFCE\",\n        \"background\": \"#000000\",\n        \"border\": \"#252525\",\n        \"danger.background\": \"#593002\",\n        \"foreground\": \"#FFFFCE\",\n        \"input.border\": \"#252525\",\n        \"list.active.background\": \"#72020222\",\n        \"list.active.border\": \"#720202\",\n        \"list.even.background\": \"#090909\",\n        \"list.hover.background\": \"#111111\",\n        \"muted.background\": \"#20202099\",\n        \"muted.foreground\": \"#828282\",\n        \"panel.background\": \"#1e1e1e\",\n        \"popover.background\": \"#090909\",\n        \"popover.foreground\": \"#FFFFCE\",\n        \"primary.background\": \"#720202\",\n        \"primary.foreground\": \"#FFFFCE\",\n        \"scrollbar.background\": \"#1e1e1e00\",\n        \"scrollbar.thumb.background\": \"#3e3e3e\",\n        \"secondary.background\": \"#202020\",\n        \"secondary.active.background\": \"#202020DD\",\n        \"secondary.foreground\": \"#979797\",\n        \"secondary.hover.background\": \"#20202099\",\n        \"tab.foreground\": \"#808080\",\n        \"title_bar.background\": \"#0e0e0e\",\n        \"table.background\": \"#1e1e1e00\",\n        \"table.hover.background\": \"#1E1E1E\",\n        \"ring\": \"#570101\",\n        \"base.red\": \"#723202\",\n        \"base.red.light\": \"#c97636\",\n        \"base.green\": \"#027225\",\n        \"base.green.light\": \"#39c936\",\n        \"base.yellow\": \"#726302\",\n        \"base.yellow.light\": \"#c9b536\",\n        \"base.blue\": \"#022d72\",\n        \"base.blue.light\": \"#0551cb\",\n        \"base.magenta\": \"#3a286c\",\n        \"base.magenta.light\": \"#58197a\",\n        \"base.cyan\": \"#027272\",\n        \"base.cyan.light\": \"#36c9c9\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#FFFFCE\",\n        \"editor.background\": \"#000000\",\n        \"editor.active_line.background\": \"#1e1e1e\",\n        \"editor.line_number\": \"#828282\",\n        \"editor.invisible\": \"#82828266\",\n        \"warning.border\": \"#720202\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#fecf75\"\n          },\n          \"boolean\": {\n            \"color\": \"#fd9f4d\"\n          },\n          \"comment\": {\n            \"color\": \"#828282\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#828282\"\n          },\n          \"constant\": {\n            \"color\": \"#fd9f4d\"\n          },\n          \"constructor\": {\n            \"color\": \"#cb4a05\"\n          },\n          \"embedded\": {\n            \"color\": \"#dcdcdc\"\n          },\n          \"function\": {\n            \"color\": \"#fd9f4d\"\n          },\n          \"keyword\": {\n            \"color\": \"#cb4a05\"\n          },\n          \"link_text\": {\n            \"color\": \"#cda074\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#9e744d\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#cb4a05\"\n          },\n          \"string\": {\n            \"color\": \"#cc734d\"\n          },\n          \"string.escape\": {\n            \"color\": \"#cc734d\"\n          },\n          \"string.regex\": {\n            \"color\": \"#cc734d\"\n          },\n          \"string.special\": {\n            \"color\": \"#fd9f4d\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#fd9f4d\"\n          },\n          \"tag\": {\n            \"color\": \"#cb4a05\"\n          },\n          \"text.literal\": {\n            \"color\": \"#fd9f4d\"\n          },\n          \"title\": {\n            \"color\": \"#cda074\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#cda074\"\n          },\n          \"property\": {\n            \"color\": \"#dcdcdc\"\n          },\n          \"variable.special\": {\n            \"color\": \"#cb4a05\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/flexoki.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Flexoki\",\n  \"author\": \"kepano\",\n  \"url\": \"https://github.com/kepano/flexoki\",\n  \"themes\": [\n    {\n      \"name\": \"Flexoki Light\",\n      \"mode\": \"light\",\n      \"colors\": {\n        \"accent.background\": \"#E8E4CE\",\n        \"accent.foreground\": \"#100F0F\",\n        \"background\": \"#FFFCF0\",\n        \"foreground\": \"#100F0F\",\n        \"border\": \"#E6E4D9\",\n        \"ring\": \"#D0A215\",\n        \"selection.background\": \"#D0A21577\",\n        \"input.border\": \"#E6E4D9\",\n        \"list.active.background\": \"#D0A21520\",\n        \"list.active.border\": \"#D0A215\",\n        \"list.background\": \"#FFFCF0\",\n        \"list.even.background\": \"#F2F0E5\",\n        \"muted.background\": \"#F2F0E5\",\n        \"muted.foreground\": \"#6F6E69\",\n        \"primary.background\": \"#3AA99F\",\n        \"primary.foreground\": \"#FAFAFA\",\n        \"info.background\": \"#4385BE\",\n        \"scrollbar.background\": \"#FAFAFA00\",\n        \"scrollbar.thumb.background\": \"#E6E4D9\",\n        \"scrollbar.thumb.hover.background\": \"#DAD8CE\",\n        \"secondary.active.background\": \"#DAD8CE\",\n        \"secondary.background\": \"#F2F0E5\",\n        \"secondary.foreground\": \"#100F0F\",\n        \"secondary.hover.background\": \"#E6E4D9\",\n        \"tab.active.background\": \"#FFFCF0\",\n        \"tab.active.foreground\": \"#100F0F\",\n        \"tab.background\": \"#ECECED00\",\n        \"tab.foreground\": \"#8d8986\",\n        \"tab_bar.background\": \"#F2F0E5\",\n        \"title_bar.background\": \"#F2F0E5\",\n        \"title_bar.border\": \"#DAD8CE\",\n        \"base.yellow\": \"#D0A215\",\n        \"base.red\": \"#D14D41\",\n        \"base.blue\": \"#4385BE\",\n        \"base.green\": \"#879A39\",\n        \"base.magenta\": \"#CE5D97\",\n        \"base.cyan\": \"#3AA99F\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#100F0F\",\n        \"editor.background\": \"#FFFCF0\",\n        \"editor.active_line.background\": \"#F2F0E5\",\n        \"editor.line_number\": \"#B7B5AC\",\n        \"editor.active_line_number\": \"#24837B\",\n        \"editor.invisible\": \"#B7B5AC66\",\n        \"conflict\": \"#AD8301\",\n        \"conflict.background\": \"#FAEEC6\",\n        \"conflict.border\": \"#AD8301\",\n        \"created\": \"#66800B\",\n        \"created.background\": \"#EDEECF\",\n        \"created.border\": \"#66800B\",\n        \"deleted\": \"#AF3029\",\n        \"deleted.background\": \"#FFE1D5\",\n        \"deleted.border\": \"#AF3029\",\n        \"error\": \"#AF3029\",\n        \"error.background\": \"#FFE1D5\",\n        \"error.border\": \"#AF3029\",\n        \"hidden\": \"#B7B5AC\",\n        \"hidden.background\": \"#F2F0E5\",\n        \"hidden.border\": \"#B7B5AC\",\n        \"hint\": \"#B7B5AC\",\n        \"hint.background\": \"#F2F0E5\",\n        \"hint.border\": \"#B7B5AC\",\n        \"ignored\": \"#B7B5AC\",\n        \"ignored.background\": \"#F2F0E5\",\n        \"ignored.border\": \"#B7B5AC\",\n        \"info\": \"#24837B\",\n        \"info.background\": \"#DDF1E4\",\n        \"info.border\": \"#24837B\",\n        \"modified\": \"#AD8301\",\n        \"modified.background\": \"#FAEEC6\",\n        \"modified.border\": \"#AD8301\",\n        \"predictive\": \"#B7B5AC\",\n        \"renamed\": \"#24837B\",\n        \"renamed.background\": \"#DDF1E4\",\n        \"renamed.border\": \"#24837B\",\n        \"success\": \"#66800B\",\n        \"success.background\": \"#EDEECF\",\n        \"success.border\": \"#66800B\",\n        \"unreachable\": \"#6F6E69\",\n        \"unreachable.background\": \"#F2F0E5\",\n        \"unreachable.border\": \"#6F6E69\",\n        \"warning\": \"#AD8301\",\n        \"warning.background\": \"#FAEEC6\",\n        \"warning.border\": \"#AD8301\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#205EA6\"\n          },\n          \"boolean\": {\n            \"color\": \"#BC5215\"\n          },\n          \"comment\": {\n            \"color\": \"#B7B5AC\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#B7B5AC\"\n          },\n          \"constant\": {\n            \"color\": \"#F07171\"\n          },\n          \"constructor\": {\n            \"color\": \"#205EA6\"\n          },\n          \"embedded\": {\n            \"color\": \"#5C6773\"\n          },\n          \"function\": {\n            \"color\": \"#BC5215\"\n          },\n          \"keyword\": {\n            \"color\": \"#66800B\"\n          },\n          \"link_text\": {\n            \"color\": \"#24837B\"\n          },\n          \"link_uri\": {\n            \"color\": \"#24837B\"\n          },\n          \"number\": {\n            \"color\": \"#5E409D\"\n          },\n          \"string\": {\n            \"color\": \"#24837B\"\n          },\n          \"string.escape\": {\n            \"color\": \"#24837B\"\n          },\n          \"string.regex\": {\n            \"color\": \"#24837B\"\n          },\n          \"string.special\": {\n            \"color\": \"#24837B\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#24837B\"\n          },\n          \"tag\": {\n            \"color\": \"#205EA6\"\n          },\n          \"text.literal\": {\n            \"color\": \"#24837B\"\n          },\n          \"title\": {\n            \"color\": \"#24837B\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#AD8301\"\n          },\n          \"property\": {\n            \"color\": \"#BC5215\"\n          },\n          \"variable.special\": {\n            \"color\": \"#205EA6\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Flexoki Dark\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#242221\",\n        \"accent.foreground\": \"#CECDC3\",\n        \"background\": \"#100F0F\",\n        \"border\": \"#282726\",\n        \"ring\": \"#AD8301\",\n        \"selection.background\": \"#D0A21577\",\n        \"foreground\": \"#CECDC3\",\n        \"input.border\": \"#282726\",\n        \"list.active.background\": \"#D0A21515\",\n        \"list.active.border\": \"#AD8301\",\n        \"list.even.background\": \"#1C1B1A\",\n        \"muted.background\": \"#1C1B1A\",\n        \"muted.foreground\": \"#878580\",\n        \"panel.background\": \"#1C1B1A\",\n        \"popover.background\": \"#100F0F\",\n        \"popover.foreground\": \"#CECDC3\",\n        \"primary.background\": \"#24837B\",\n        \"primary.foreground\": \"#FFFCF0\",\n        \"info.background\": \"#205EA6\",\n        \"scrollbar.background\": \"#100F0F00\",\n        \"scrollbar.thumb.background\": \"#282726\",\n        \"scrollbar.thumb.hover.background\": \"#343331\",\n        \"secondary.background\": \"#232221\",\n        \"secondary.active.background\": \"#262423\",\n        \"secondary.foreground\": \"#CECDC3\",\n        \"secondary.hover.background\": \"#232221\",\n        \"tab.active.background\": \"#100F0F\",\n        \"tab.active.foreground\": \"#CECDC3\",\n        \"tab.background\": \"#1C1B1A\",\n        \"tab.foreground\": \"#878580\",\n        \"tab_bar.background\": \"#1C1B1A99\",\n        \"title_bar.background\": \"#191818\",\n        \"base.yellow\": \"#AD8301\",\n        \"base.yellow.light\": \"#D0A215\",\n        \"base.red\": \"#AF3029\",\n        \"base.red.light\": \"#D14D41\",\n        \"base.blue\": \"#205EA6\",\n        \"base.blue.light\": \"#4385BE\",\n        \"base.green\": \"#66800B\",\n        \"base.green.light\": \"#879A39\",\n        \"base.magenta\": \"#A02F6F\",\n        \"base.magenta.light\": \"#CE5D97\",\n        \"base.cyan\": \"#24837B\",\n        \"base.cyan.light\": \"#3AA99F\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#CECDC3\",\n        \"editor.background\": \"#100F0F\",\n        \"editor.active_line.background\": \"#1C1B1A\",\n        \"editor.line_number\": \"#575653\",\n        \"editor.active_line_number\": \"#3AA99F\",\n        \"editor.invisible\": \"#B7B5AC66\",\n        \"conflict\": \"#D0A215\",\n        \"created\": \"#879A39\",\n        \"hidden\": \"#575653\",\n        \"hint\": \"#575653\",\n        \"modified\": \"#D0A215\",\n        \"predictive\": \"#878580\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#4385BE\"\n          },\n          \"boolean\": {\n            \"color\": \"#DA702C\"\n          },\n          \"comment\": {\n            \"color\": \"#575653\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#575653\"\n          },\n          \"constant\": {\n            \"color\": \"#CE5D97\"\n          },\n          \"constructor\": {\n            \"color\": \"#4385BE\"\n          },\n          \"embedded\": {\n            \"color\": \"#878580\"\n          },\n          \"function\": {\n            \"color\": \"#DA702C\"\n          },\n          \"keyword\": {\n            \"color\": \"#879A39\"\n          },\n          \"link_text\": {\n            \"color\": \"#3AA99F\"\n          },\n          \"link_uri\": {\n            \"color\": \"#3AA99F\"\n          },\n          \"number\": {\n            \"color\": \"#8B7EC8\"\n          },\n          \"string\": {\n            \"color\": \"#3AA99F\"\n          },\n          \"string.escape\": {\n            \"color\": \"#3AA99F\"\n          },\n          \"string.regex\": {\n            \"color\": \"#3AA99F\"\n          },\n          \"string.special\": {\n            \"color\": \"#3AA99F\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#3AA99F\"\n          },\n          \"tag\": {\n            \"color\": \"#4385BE\"\n          },\n          \"text.literal\": {\n            \"color\": \"#3AA99F\"\n          },\n          \"title\": {\n            \"color\": \"#D0A215\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#D0A215\"\n          },\n          \"property\": {\n            \"color\": \"#DA702C\"\n          },\n          \"variable.special\": {\n            \"color\": \"#4385BE\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/gruvbox.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Gruvbox\",\n  \"author\": \"Pavel Pertsev\",\n  \"url\": \"https://github.com/morhetz/gruvbox\",\n  \"themes\": [\n    {\n      \"name\": \"Gruvbox Light\",\n      \"mode\": \"light\",\n      \"colors\": {\n        \"accent.background\": \"#e6d19e\",\n        \"accent.foreground\": \"#3c3836\",\n        \"background\": \"#fbf1c7\",\n        \"border\": \"#d5c4a1\",\n        \"danger.background\": \"#fb4934\",\n        \"danger.active.background\": \"#cc241d\",\n        \"danger.foreground\": \"#fbf1c7\",\n        \"foreground\": \"#3c3836\",\n        \"input.border\": \"#d5c4a1\",\n        \"link.active.foreground\": \"#458588\",\n        \"link.foreground\": \"#458588\",\n        \"link.hover.foreground\": \"#458588\",\n        \"list.active.background\": \"#d7992122\",\n        \"list.hover.background\": \"#ebdbb2\",\n        \"list.active.border\": \"#d79921\",\n        \"list.even.background\": \"#f9ecba\",\n        \"muted.background\": \"#d5c4a166\",\n        \"muted.foreground\": \"#928374\",\n        \"panel.background\": \"#ebdbb2\",\n        \"popover.background\": \"#fbf1c7\",\n        \"popover.foreground\": \"#3c3836\",\n        \"primary.active.background\": \"#b7841f\",\n        \"primary.background\": \"#d79921\",\n        \"primary.foreground\": \"#fbf1c7\",\n        \"primary.hover.background\": \"#d79921ee\",\n        \"ring\": \"#d79921\",\n        \"scrollbar.background\": \"#fbf1c700\",\n        \"scrollbar.thumb.background\": \"#d5c4a1\",\n        \"secondary.background\": \"#EBDBB2\",\n        \"secondary.active.background\": \"#d5c4a1\",\n        \"secondary.foreground\": \"#3c3836\",\n        \"secondary.hover.background\": \"#e8d5a6\",\n        \"tab.active.background\": \"#fbf1c7\",\n        \"tab.active.foreground\": \"#3c3836\",\n        \"tab.background\": \"#ebdbb200\",\n        \"tab.foreground\": \"#928374\",\n        \"tab_bar.background\": \"#ebdbb2\",\n        \"title_bar.background\": \"#ebdbb2\",\n        \"title_bar.border\": \"#d5c4a1\",\n        \"base.magenta.light\": \"#D3869B66\",\n        \"base.red\": \"#fb4934\",\n        \"base.green\": \"#67a64f\",\n        \"base.yellow\": \"#b57614\",\n        \"base.blue\": \"#076678\",\n        \"base.magenta\": \"#8f3f71\",\n        \"base.cyan\": \"#427b58\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#3c3836\",\n        \"editor.background\": \"#fbf1c7\",\n        \"editor.active_line.background\": \"#ebdbb2\",\n        \"editor.line_number\": \"#928374\",\n        \"editor.active_line_number\": \"#3c3836\",\n        \"editor.invisible\": \"#92837466\",\n        \"conflict\": \"#cc241d\",\n        \"created\": \"#79740e\",\n        \"deleted\": \"#9d0006\",\n        \"error\": \"#cc241d\",\n        \"hidden\": \"#928374\",\n        \"hint\": \"#458588\",\n        \"ignored\": \"#928374\",\n        \"info\": \"#458588\",\n        \"modified\": \"#b57614\",\n        \"predictive\": \"#928374\",\n        \"renamed\": \"#b57614\",\n        \"success\": \"#79740e\",\n        \"unreachable\": \"#928374\",\n        \"warning\": \"#d79921\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#b57614\"\n          },\n          \"boolean\": {\n            \"color\": \"#d79921\"\n          },\n          \"comment\": {\n            \"color\": \"#928374\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#928374\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#d79921\"\n          },\n          \"constructor\": {\n            \"color\": \"#b57614\"\n          },\n          \"embedded\": {\n            \"color\": \"#3c3836\"\n          },\n          \"function\": {\n            \"color\": \"#d79921\"\n          },\n          \"keyword\": {\n            \"color\": \"#cc241d\"\n          },\n          \"link_text\": {\n            \"color\": \"#458588\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#076678\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#cc241d\"\n          },\n          \"string\": {\n            \"color\": \"#79740e\"\n          },\n          \"string.escape\": {\n            \"color\": \"#79740e\"\n          },\n          \"string.regex\": {\n            \"color\": \"#79740e\"\n          },\n          \"string.special\": {\n            \"color\": \"#d79921\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#d79921\"\n          },\n          \"tag\": {\n            \"color\": \"#b57614\"\n          },\n          \"text.literal\": {\n            \"color\": \"#d79921\"\n          },\n          \"title\": {\n            \"color\": \"#b57614\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#b57614\"\n          },\n          \"property\": {\n            \"color\": \"#3c3836\"\n          },\n          \"variable.special\": {\n            \"color\": \"#cc241d\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Gruvbox Dark\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#303030\",\n        \"accent.foreground\": \"#ebdbb2\",\n        \"background\": \"#1d2021\",\n        \"border\": \"#3e3936\",\n        \"danger.active.background\": \"#fb4934\",\n        \"foreground\": \"#ebdbb2\",\n        \"input.border\": \"#504945\",\n        \"link.active.foreground\": \"#83a598\",\n        \"link.foreground\": \"#83a598\",\n        \"link.hover.foreground\": \"#83a598\",\n        \"list.active.background\": \"#d7992111\",\n        \"list.active.border\": \"#b17f1b\",\n        \"list.even.background\": \"#282828\",\n        \"muted.background\": \"#3c383655\",\n        \"muted.foreground\": \"#928374\",\n        \"panel.background\": \"#282828\",\n        \"popover.background\": \"#1d2021\",\n        \"popover.foreground\": \"#ebdbb2\",\n        \"primary.background\": \"#d79921\",\n        \"primary.foreground\": \"#282828\",\n        \"primary.hover.background\": \"#fabd2f\",\n        \"ring\": \"#b17f1b\",\n        \"scrollbar.background\": \"#1d202100\",\n        \"scrollbar.thumb.background\": \"#504945\",\n        \"secondary.active.background\": \"#333332\",\n        \"secondary.background\": \"#282828\",\n        \"secondary.foreground\": \"#ebdbb2\",\n        \"secondary.hover.background\": \"#33333299\",\n        \"tab.active.background\": \"#1d2021\",\n        \"tab.active.foreground\": \"#ebdbb2\",\n        \"tab.background\": \"#28282800\",\n        \"tab.foreground\": \"#928374\",\n        \"tab_bar.background\": \"#28282899\",\n        \"title_bar.background\": \"#252525\",\n        \"title_bar.border\": \"#3e3936\",\n        \"base.magenta.light\": \"#D3869B66\",\n        \"base.red\": \"#f06555\",\n        \"base.green\": \"#98971a\",\n        \"base.yellow\": \"#d79921\",\n        \"base.blue\": \"#458588\",\n        \"base.magenta\": \"#b16286\",\n        \"base.cyan\": \"#689d6a\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#DDDDDD\",\n        \"editor.background\": \"#1d2021\",\n        \"editor.active_line.background\": \"#131313\",\n        \"editor.line_number\": \"#8F8F8F\",\n        \"editor.active_line_number\": \"#DDDDDD\",\n        \"editor.invisible\": \"#92837466\",\n        \"conflict\": \"#f06555\",\n        \"created\": \"#3f72e2\",\n        \"deleted\": \"#9d0006\",\n        \"error\": \"#cc241d\",\n        \"hidden\": \"#9E9E9E\",\n        \"hint\": \"#b283f8\",\n        \"ignored\": \"#928374\",\n        \"info\": \"#458588\",\n        \"modified\": \"#B0A878\",\n        \"predictive\": \"#5D5945\",\n        \"renamed\": \"#b57614\",\n        \"success\": \"#79740e\",\n        \"unreachable\": \"#928374\",\n        \"warning\": \"#d79921\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#be9a52\"\n          },\n          \"boolean\": {\n            \"color\": \"#E1D797\"\n          },\n          \"comment\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"constant\": {\n            \"color\": \"#E1D797\"\n          },\n          \"constructor\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"embedded\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"function\": {\n            \"color\": \"#E1D797\"\n          },\n          \"keyword\": {\n            \"color\": \"#E19773\"\n          },\n          \"link_text\": {\n            \"color\": \"#A86D3B\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#6F6D66\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#E19773\"\n          },\n          \"string\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.escape\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.regex\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.special\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#E1D797\"\n          },\n          \"tag\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"text.literal\": {\n            \"color\": \"#E1D797\"\n          },\n          \"title\": {\n            \"color\": \"#A76D3B\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#A86D3B\"\n          },\n          \"property\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"variable.special\": {\n            \"color\": \"#E19773\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/harper.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Harper\",\n  \"author\": \"iTerm2-Color-Schemes\",\n  \"url\": \"https://github.com/mbadolato/iTerm2-Color-Schemes\",\n  \"themes\": [\n    {\n      \"name\": \"Harper\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#27222c\",\n        \"accent.foreground\": \"#a8a49d\",\n        \"background\": \"#010101\",\n        \"border\": \"#333333\",\n        \"foreground\": \"#a8a49d\",\n        \"input.border\": \"#333333\",\n        \"list.active.background\": \"#B196C622\",\n        \"list.active.border\": \"#B196C699\",\n        \"list.even.background\": \"#18151B77\",\n        \"muted.background\": \"#171717\",\n        \"muted.foreground\": \"#726E69\",\n        \"panel.background\": \"#003a5b\",\n        \"popover.background\": \"#010101\",\n        \"popover.foreground\": \"#a8a49d\",\n        \"primary.background\": \"#B196C6\",\n        \"primary.foreground\": \"#000000\",\n        \"scrollbar.background\": \"#003a5b00\",\n        \"scrollbar.thumb.background\": \"#726E69\",\n        \"secondary.background\": \"#1b1b1b\",\n        \"secondary.active.background\": \"#2c2632\",\n        \"secondary.foreground\": \"#A8A49D\",\n        \"secondary.hover.background\": \"#23232399\",\n        \"tab.active.background\": \"#010101\",\n        \"tab.active.foreground\": \"#a8a49d\",\n        \"tab.foreground\": \"#787878\",\n        \"title_bar.background\": \"#101010\",\n        \"base.red\": \"#ff5874\",\n        \"base.red.light\": \"#ff587499\",\n        \"base.green\": \"#489e48\",\n        \"base.green.light\": \"#489e4899\",\n        \"base.blue\": \"#7fb5e1\",\n        \"base.blue.light\": \"#7fb5e199\",\n        \"base.cyan\": \"#bff5e5\",\n        \"base.cyan.light\": \"#bff5e599\",\n        \"base.magenta\": \"#b296c6\",\n        \"base.magenta.light\": \"#b296c699\",\n        \"base.yellow\": \"#f8b63f\",\n        \"base.yellow.light\": \"#f8b63f99\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#a8a49d\",\n        \"editor.background\": \"#010101\",\n        \"editor.active_line.background\": \"#18151B\",\n        \"editor.line_number\": \"#726E69\",\n        \"editor.active_line_number\": \"#a8a49d\",\n        \"editor.invisible\": \"#726E6966\",\n        \"conflict\": \"#ff5874\",\n        \"created\": \"#489e48\",\n        \"deleted\": \"#ff5874\",\n        \"error\": \"#ff5874\",\n        \"hidden\": \"#726E69\",\n        \"hint\": \"#7fb5e1\",\n        \"ignored\": \"#726E69\",\n        \"info\": \"#7fb5e1\",\n        \"modified\": \"#b296c6\",\n        \"predictive\": \"#726E69\",\n        \"renamed\": \"#b296c6\",\n        \"success\": \"#489e48\",\n        \"unreachable\": \"#726E69\",\n        \"warning\": \"#f8b63f\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#b296c6\"\n          },\n          \"boolean\": {\n            \"color\": \"#f8b63f\"\n          },\n          \"comment\": {\n            \"color\": \"#726E69\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#726E69\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#f8b63f\"\n          },\n          \"constructor\": {\n            \"color\": \"#b296c6\"\n          },\n          \"embedded\": {\n            \"color\": \"#a8a49d\"\n          },\n          \"function\": {\n            \"color\": \"#f8b63f\"\n          },\n          \"keyword\": {\n            \"color\": \"#ff5874\"\n          },\n          \"link_text\": {\n            \"color\": \"#7fb5e1\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#bff5e5\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#ff5874\"\n          },\n          \"string\": {\n            \"color\": \"#489e48\"\n          },\n          \"string.escape\": {\n            \"color\": \"#489e48\"\n          },\n          \"string.regex\": {\n            \"color\": \"#489e48\"\n          },\n          \"string.special\": {\n            \"color\": \"#f8b63f\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#f8b63f\"\n          },\n          \"tag\": {\n            \"color\": \"#b296c6\"\n          },\n          \"text.literal\": {\n            \"color\": \"#f8b63f\"\n          },\n          \"title\": {\n            \"color\": \"#b296c6\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#b296c6\"\n          },\n          \"property\": {\n            \"color\": \"#a8a49d\"\n          },\n          \"variable.special\": {\n            \"color\": \"#ff5874\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/hybrid.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Hybrid\",\n  \"author\": \"Andrew Wong\",\n  \"url\": \"https://github.com/w0ng/vim-hybrid\",\n  \"themes\": [\n    {\n      \"name\": \"Hybrid Light\",\n      \"mode\": \"light\",\n      \"shadow\": false,\n      \"colors\": {\n        \"accent.background\": \"#c8ced1\",\n        \"accent.foreground\": \"#1c1c1c\",\n        \"background\": \"#E4E4E4\",\n        \"border\": \"#CACACA\",\n        \"danger.background\": \"#ff5f5f\",\n        \"danger.foreground\": \"#ffffff\",\n        \"foreground\": \"#1c1c1c\",\n        \"input.border\": \"#CACACA\",\n        \"link.active.foreground\": \"#005f87\",\n        \"link.foreground\": \"#005f87\",\n        \"link.hover.foreground\": \"#005f87\",\n        \"list.active.background\": \"#79792C20\",\n        \"list.active.border\": \"#79792C\",\n        \"list.hover.background\": \"#D5D5D5\",\n        \"list.even.background\": \"#DFDFDF\",\n        \"muted.background\": \"#d7d7d7\",\n        \"muted.foreground\": \"#5f5f5f\",\n        \"primary.background\": \"#005f87\",\n        \"primary.foreground\": \"#ffffff\",\n        \"scrollbar.background\": \"#E0E0E000\",\n        \"scrollbar.thumb.background\": \"#8f8f8f\",\n        \"secondary.background\": \"#D0D0D0\",\n        \"secondary.active.background\": \"#c4c4c4\",\n        \"secondary.foreground\": \"#1c1c1c\",\n        \"secondary.hover.background\": \"#c4c4c499\",\n        \"tab.active.background\": \"#E4E4E4\",\n        \"tab.active.foreground\": \"#1c1c1c\",\n        \"tab.background\": \"#e8e8e800\",\n        \"tab.foreground\": \"#5f5f5f\",\n        \"tab_bar.background\": \"#e8e8e8\",\n        \"title_bar.background\": \"#D0D0D0\",\n        \"title_bar.border\": \"#B3B3B3\",\n        \"base.red\": \"#5F0000\",\n        \"base.green\": \"#005F00\",\n        \"base.yellow\": \"#948000\",\n        \"base.blue\": \"#00195f\",\n        \"base.magenta\": \"#5f1c51\",\n        \"base.magenta.light\": \"#5f1c5111\",\n        \"base.cyan\": \"#005a5f\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#1c1c1c\",\n        \"editor.background\": \"#E4E4E4\",\n        \"editor.active_line.background\": \"#D3D3D3\",\n        \"editor.line_number\": \"#5F5F5F\",\n        \"editor.active_line_number\": \"#1C1C1C\",\n        \"editor.invisible\": \"#5F5F5F66\",\n        \"conflict\": \"#D2602D\",\n        \"created\": \"#3F72E2\",\n        \"deleted\": \"#FF5F5F\",\n        \"error\": \"#FF5F5F\",\n        \"hidden\": \"#9E9E9E\",\n        \"hint\": \"#5F87AF\",\n        \"ignored\": \"#B3B3B3\",\n        \"info\": \"#005F87\",\n        \"modified\": \"#B0A878\",\n        \"predictive\": \"#5D5945\",\n        \"renamed\": \"#5F87AF\",\n        \"success\": \"#005F00\",\n        \"unreachable\": \"#9E9E9E\",\n        \"warning\": \"#948000\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#948000\"\n          },\n          \"boolean\": {\n            \"color\": \"#005F00\"\n          },\n          \"comment\": {\n            \"color\": \"#5F5F5F\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#5F5F5F\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#005F87\"\n          },\n          \"constructor\": {\n            \"color\": \"#79792C\"\n          },\n          \"embedded\": {\n            \"color\": \"#1C1C1C\"\n          },\n          \"function\": {\n            \"color\": \"#005F87\"\n          },\n          \"keyword\": {\n            \"color\": \"#5F1C51\"\n          },\n          \"link_text\": {\n            \"color\": \"#005F87\",\n            \"font_style\": \"underline\"\n          },\n          \"link_uri\": {\n            \"color\": \"#005F87\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#005F00\"\n          },\n          \"string\": {\n            \"color\": \"#005F00\"\n          },\n          \"string.escape\": {\n            \"color\": \"#005F00\"\n          },\n          \"string.regex\": {\n            \"color\": \"#005F00\"\n          },\n          \"string.special\": {\n            \"color\": \"#005F87\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#005F87\"\n          },\n          \"tag\": {\n            \"color\": \"#79792C\"\n          },\n          \"text.literal\": {\n            \"color\": \"#005F87\"\n          },\n          \"title\": {\n            \"color\": \"#005F87\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#5F1C51\"\n          },\n          \"property\": {\n            \"color\": \"#1C1C1C\"\n          },\n          \"variable.special\": {\n            \"color\": \"#5F1C51\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Hybrid Dark\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#31393a\",\n        \"accent.foreground\": \"#e8e8e8\",\n        \"background\": \"#1D1F21\",\n        \"border\": \"#34373c\",\n        \"foreground\": \"#e8e8e8\",\n        \"input.border\": \"#34373c\",\n        \"link.active.foreground\": \"#87d7ff\",\n        \"link.foreground\": \"#81A2BE\",\n        \"link.hover.foreground\": \"#006f9f\",\n        \"list.active.background\": \"#15678a10\",\n        \"list.active.border\": \"#15678a\",\n        \"list.even.background\": \"#282A2E\",\n        \"muted.background\": \"#282A2E99\",\n        \"muted.foreground\": \"#878787\",\n        \"panel.background\": \"#1D1F21\",\n        \"popover.background\": \"#1D1F21\",\n        \"popover.foreground\": \"#e8e8e8\",\n        \"primary.background\": \"#15678a\",\n        \"primary.foreground\": \"#e8e8e8\",\n        \"scrollbar.background\": \"#1D1F2100\",\n        \"scrollbar.thumb.background\": \"#5f5f5f\",\n        \"secondary.background\": \"#303336\",\n        \"secondary.active.background\": \"#33363b\",\n        \"secondary.foreground\": \"#e8e8e8\",\n        \"secondary.hover.background\": \"#373b3e\",\n        \"tab.active.background\": \"#1D1F21\",\n        \"tab.active.foreground\": \"#d4d4d4\",\n        \"tab.background\": \"#24262700\",\n        \"tab.foreground\": \"#999999\",\n        \"tab_bar.background\": \"#242627\",\n        \"title_bar.background\": \"#242629\",\n        \"title_bar.border\": \"#34373c\",\n        \"base.red\": \"#8a1515\",\n        \"base.red.light\": \"#ff5f5f\",\n        \"base.green\": \"#7c8a15\",\n        \"base.green.light\": \"#b3bf5a\",\n        \"base.yellow\": \"#8a7c15\",\n        \"base.yellow.light\": \"#e4b55e\",\n        \"base.blue\": \"#15678a\",\n        \"base.blue.light\": \"#6e90b0\",\n        \"base.magenta\": \"#8a1567\",\n        \"base.magenta.light\": \"#B294BB\",\n        \"base.cyan\": \"#15678a\",\n        \"base.cyan.light\": \"#7fbfb4\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#DDDDDD\",\n        \"editor.background\": \"#000000\",\n        \"editor.active_line.background\": \"#131313\",\n        \"editor.line_number\": \"#8F8F8F\",\n        \"editor.active_line_number\": \"#DDDDDD\",\n        \"editor.invisible\": \"#5F5F5F66\",\n        \"conflict\": \"#D2602D\",\n        \"created\": \"#3f72e2\",\n        \"created.background\": \"#0C4619\",\n        \"deleted.background\": \"#46190C\",\n        \"error.background\": \"#46190C\",\n        \"error.border\": \"#802207\",\n        \"hidden\": \"#9E9E9E\",\n        \"hint\": \"#b283f8\",\n        \"hint.background\": \"#250c4b\",\n        \"hint.border\": \"#3f0891\",\n        \"info.background\": \"#0C194D\",\n        \"info.border\": \"#082190\",\n        \"modified\": \"#B0A878\",\n        \"modified.background\": \"#3A310E\",\n        \"predictive\": \"#5D5945\",\n        \"success.background\": \"#0C4619\",\n        \"warning.background\": \"#3A310E\",\n        \"warning.border\": \"#7B6508\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#be9a52\"\n          },\n          \"boolean\": {\n            \"color\": \"#E1D797\"\n          },\n          \"comment\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"constant\": {\n            \"color\": \"#E1D797\"\n          },\n          \"constructor\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"embedded\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"function\": {\n            \"color\": \"#E1D797\"\n          },\n          \"keyword\": {\n            \"color\": \"#E19773\"\n          },\n          \"link_text\": {\n            \"color\": \"#A86D3B\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#6F6D66\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#E19773\"\n          },\n          \"string\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.escape\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.regex\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.special\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#E1D797\"\n          },\n          \"tag\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"text.literal\": {\n            \"color\": \"#E1D797\"\n          },\n          \"title\": {\n            \"color\": \"#A76D3B\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#A86D3B\"\n          },\n          \"property\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"variable.special\": {\n            \"color\": \"#E19773\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/jellybeans.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Jellybeans\",\n  \"author\": \"nanotech\",\n  \"url\": \"https://github.com/nanotech/jellybeans.vim\",\n  \"themes\": [\n    {\n      \"name\": \"Jellybeans\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#292929\",\n        \"accent.foreground\": \"#e8e8e8\",\n        \"background\": \"#151515\",\n        \"border\": \"#2e2e2e\",\n        \"danger.foreground\": \"#151515\",\n        \"foreground\": \"#E8E8D3\",\n        \"input.border\": \"#4E4847\",\n        \"link.active.foreground\": \"#97bedc\",\n        \"link.foreground\": \"#97bedc\",\n        \"link.hover.foreground\": \"#97bedc\",\n        \"list.active.background\": \"#97bedc15\",\n        \"list.active.border\": \"#97bedc\",\n        \"list.even.background\": \"#1C1C1C\",\n        \"muted.background\": \"#242424\",\n        \"muted.foreground\": \"#767676\",\n        \"panel.background\": \"#151515\",\n        \"popover.background\": \"#151515\",\n        \"popover.foreground\": \"#e8e8e8\",\n        \"primary.background\": \"#97bedc\",\n        \"primary.foreground\": \"#151515\",\n        \"scrollbar.background\": \"#26262600\",\n        \"scrollbar.thumb.background\": \"#6e6e6e\",\n        \"secondary.background\": \"#2F2F2F\",\n        \"secondary.active.background\": \"#2f2f2f\",\n        \"secondary.foreground\": \"#e8e8e8\",\n        \"secondary.hover.background\": \"#252525\",\n        \"tab.active.background\": \"#151515\",\n        \"tab.active.foreground\": \"#e8e8e8\",\n        \"tab.background\": \"#10101000\",\n        \"tab.foreground\": \"#969696\",\n        \"tab_bar.background\": \"#101010\",\n        \"title_bar.background\": \"#101010\",\n        \"title_bar.border\": \"#2e2e2e\",\n        \"base.red\": \"#e27373\",\n        \"base.red.light\": \"#ffa1a1\",\n        \"base.green\": \"#94b979\",\n        \"base.green.light\": \"#bddeab\",\n        \"base.yellow\": \"#ffba7b\",\n        \"base.yellow.light\": \"#ffdca0\",\n        \"base.blue\": \"#97bedc\",\n        \"base.blue.light\": \"#b1d8f6\",\n        \"base.magenta\": \"#B294BB\",\n        \"base.magenta.light\": \"#fbdaff\",\n        \"base.cyan\": \"#00988e\",\n        \"base.cyan.light\": \"#1ab2a8\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#DDDDDD\",\n        \"editor.background\": \"#000000\",\n        \"editor.active_line.background\": \"#131313\",\n        \"editor.line_number\": \"#8F8F8F\",\n        \"editor.active_line_number\": \"#DDDDDD\",\n        \"editor.invisible\": \"#9E9E9E66\",\n        \"conflict\": \"#D2602D\",\n        \"created\": \"#3f72e2\",\n        \"hidden\": \"#9E9E9E\",\n        \"hint\": \"#b283f8\",\n        \"modified\": \"#B0A878\",\n        \"predictive\": \"#5D5945\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#be9a52\"\n          },\n          \"boolean\": {\n            \"color\": \"#E1D797\"\n          },\n          \"comment\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"constant\": {\n            \"color\": \"#E1D797\"\n          },\n          \"constructor\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"embedded\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"function\": {\n            \"color\": \"#E1D797\"\n          },\n          \"keyword\": {\n            \"color\": \"#E19773\"\n          },\n          \"link_text\": {\n            \"color\": \"#A86D3B\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#6F6D66\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#E19773\"\n          },\n          \"string\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.escape\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.regex\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.special\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#E1D797\"\n          },\n          \"tag\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"text.literal\": {\n            \"color\": \"#E1D797\"\n          },\n          \"title\": {\n            \"color\": \"#A76D3B\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#A86D3B\"\n          },\n          \"property\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"variable.special\": {\n            \"color\": \"#E19773\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/kibble.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Kibble\",\n  \"author\": \"iTerm2-Color-Schemes\",\n  \"url\": \"https://github.com/mbadolato/iTerm2-Color-Schemes\",\n  \"themes\": [\n    {\n      \"name\": \"Kibble\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#252525\",\n        \"accent.foreground\": \"#f7f7f7\",\n        \"background\": \"#0e100a\",\n        \"border\": \"#292a24\",\n        \"danger.background\": \"#e07b5c\",\n        \"foreground\": \"#f7f7f7\",\n        \"input.border\": \"#333333\",\n        \"list.active.background\": \"#2BCF1315\",\n        \"list.active.border\": \"#2BCF13\",\n        \"list.hover.background\": \"#2c292b54\",\n        \"list.even.background\": \"#24222355\",\n        \"muted.background\": \"#292a2455\",\n        \"muted.foreground\": \"#777777\",\n        \"panel.background\": \"#003a5b\",\n        \"popover.background\": \"#0e100a\",\n        \"popover.foreground\": \"#f7f7f7\",\n        \"primary.active.background\": \"#6ce05c99\",\n        \"primary.background\": \"#6ce05c\",\n        \"primary.foreground\": \"#101010\",\n        \"scrollbar.background\": \"#003a5b00\",\n        \"scrollbar.thumb.background\": \"#726E69\",\n        \"secondary.background\": \"#22231f\",\n        \"secondary.active.background\": \"#292825\",\n        \"secondary.foreground\": \"#f7f7f7\",\n        \"secondary.hover.background\": \"#22231f\",\n        \"success.background\": \"#5c6ee0\",\n        \"warning.background\": \"#e0c85c\",\n        \"info.background\": \"#5cd1e0\",\n        \"selection.background\": \"#9ba787\",\n        \"tab.active.background\": \"#0e100a\",\n        \"tab.active.foreground\": \"#f7f7f7\",\n        \"tab.foreground\": \"#969696\",\n        \"title_bar.background\": \"#161712\",\n        \"base.red\": \"#C70231\",\n        \"base.red.light\": \"#e07b5c\",\n        \"base.green\": \"#2BCF13\",\n        \"base.green.light\": \"#6ce05c\",\n        \"base.blue\": \"#3449d1\",\n        \"base.blue.light\": \"#5c6ee0\",\n        \"base.cyan\": \"#0798ab\",\n        \"base.cyan.light\": \"#5cd1e0\",\n        \"base.magenta\": \"#8400ff\",\n        \"base.magenta.light\": \"#C495F0\",\n        \"base.yellow\": \"#c7a302\",\n        \"base.yellow.light\": \"#e0c85c\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#f7f7f7\",\n        \"editor.background\": \"#0e100a\",\n        \"editor.active_line.background\": \"#242223\",\n        \"editor.line_number\": \"#726E69\",\n        \"editor.active_line_number\": \"#f7f7f7\",\n        \"editor.invisible\": \"#726E6966\",\n        \"conflict\": \"#c70031\",\n        \"created\": \"#2BCF13\",\n        \"deleted\": \"#c70031\",\n        \"error\": \"#c70031\",\n        \"hidden\": \"#726E69\",\n        \"hint\": \"#0798ab\",\n        \"ignored\": \"#726E69\",\n        \"info\": \"#0798ab\",\n        \"modified\": \"#3449d1\",\n        \"predictive\": \"#726E69\",\n        \"renamed\": \"#3449d1\",\n        \"success\": \"#2BCF13\",\n        \"unreachable\": \"#726E69\",\n        \"warning\": \"#c7a302\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#3449d1\"\n          },\n          \"boolean\": {\n            \"color\": \"#c7a302\"\n          },\n          \"comment\": {\n            \"color\": \"#726E69\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#726E69\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#c7a302\"\n          },\n          \"constructor\": {\n            \"color\": \"#3449d1\"\n          },\n          \"embedded\": {\n            \"color\": \"#f7f7f7\"\n          },\n          \"function\": {\n            \"color\": \"#c7a302\"\n          },\n          \"keyword\": {\n            \"color\": \"#c70031\"\n          },\n          \"link_text\": {\n            \"color\": \"#0798ab\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#68f2e0\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#c70031\"\n          },\n          \"string\": {\n            \"color\": \"#2BCF13\"\n          },\n          \"string.escape\": {\n            \"color\": \"#2BCF13\"\n          },\n          \"string.regex\": {\n            \"color\": \"#2BCF13\"\n          },\n          \"string.special\": {\n            \"color\": \"#c7a302\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#c7a302\"\n          },\n          \"tag\": {\n            \"color\": \"#3449d1\"\n          },\n          \"text.literal\": {\n            \"color\": \"#c7a302\"\n          },\n          \"title\": {\n            \"color\": \"#3449d1\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#3449d1\"\n          },\n          \"property\": {\n            \"color\": \"#f7f7f7\"\n          },\n          \"variable.special\": {\n            \"color\": \"#c70031\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/macos-classic.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"macOS Classic\",\n  \"author\": \"huacnlee\",\n  \"url\": \"https://github.com/huacnlee/zed-theme-macos-classic\",\n  \"themes\": [\n    {\n      \"name\": \"macOS Classic Light\",\n      \"mode\": \"light\",\n      \"shadow\": false,\n      \"colors\": {\n        \"accent.background\": \"#E0E0E0\",\n        \"accent.foreground\": \"#000000\",\n        \"background\": \"#F9F9F9\",\n        \"foreground\": \"#000000\",\n        \"border\": \"#D2D2D2\",\n        \"ring\": \"#0060de\",\n        \"danger.foreground\": \"#FFFFFF\",\n        \"list.active.background\": \"#0060de15\",\n        \"list.active.border\": \"#0060de\",\n        \"list.even.background\": \"#EFEFEF\",\n        \"list.hover.background\": \"#E7E7E7\",\n        \"muted.background\": \"#EAEAEA\",\n        \"muted.foreground\": \"#707070\",\n        \"popover.background\": \"#F5F5F5\",\n        \"popover.foreground\": \"#000000\",\n        \"primary.background\": \"#0060de\",\n        \"primary.foreground\": \"#FFFFFF\",\n        \"scrollbar.background\": \"#FFFFFF00\",\n        \"scrollbar.thumb.background\": \"#C8C8C8\",\n        \"secondary.active.background\": \"#D9D9D9\",\n        \"secondary.background\": \"#E0E0E0\",\n        \"secondary.foreground\": \"#000000\",\n        \"secondary.hover.background\": \"#E5E5E5\",\n        \"tab.active.foreground\": \"#000000\",\n        \"tab.background\": \"#E9E9E9\",\n        \"tab.foreground\": \"#606060\",\n        \"tab_bar.background\": \"#E9E9E9\",\n        \"title_bar.background\": \"#FEFEFE\",\n        \"title_bar.border\": \"#DADADA\",\n        \"base.yellow\": \"#B59A00\",\n        \"base.red\": \"#d21f07\",\n        \"base.blue\": \"#0060de\",\n        \"base.green\": \"#319a00\",\n        \"base.magenta\": \"#9A0068\",\n        \"base.cyan\": \"#007E8A\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#000000\",\n        \"editor.background\": \"#ffffff\",\n        \"editor.active_line.background\": \"#F5F5F5\",\n        \"editor.line_number\": \"#929292\",\n        \"editor.active_line_number\": \"#000000\",\n        \"editor.invisible\": \"#007fff66\",\n        \"conflict\": \"#d21f07\",\n        \"created\": \"#0060de\",\n        \"hidden\": \"#6D6D6D\",\n        \"hint\": \"#9A0068\",\n        \"modified\": \"#319a00\",\n        \"predictive\": \"#A4ABB6\",\n        \"warning\": \"#B59A00\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#957931\"\n          },\n          \"boolean\": {\n            \"color\": \"#C5060B\"\n          },\n          \"comment\": {\n            \"color\": \"#007fff\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#007fff\"\n          },\n          \"constant\": {\n            \"color\": \"#C5060B\"\n          },\n          \"constructor\": {\n            \"color\": \"#0433ff\"\n          },\n          \"embedded\": {\n            \"color\": \"#333333\"\n          },\n          \"function\": {\n            \"color\": \"#0000A2\"\n          },\n          \"keyword\": {\n            \"color\": \"#0433ff\"\n          },\n          \"link_text\": {\n            \"color\": \"#0000A2\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#6A7293\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#0433ff\"\n          },\n          \"string\": {\n            \"color\": \"#036A07\"\n          },\n          \"string.escape\": {\n            \"color\": \"#036A07\"\n          },\n          \"string.regex\": {\n            \"color\": \"#036A07\"\n          },\n          \"string.special\": {\n            \"color\": \"#d21f07\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#d21f07\"\n          },\n          \"tag\": {\n            \"color\": \"#0433ff\"\n          },\n          \"text.literal\": {\n            \"color\": \"#6F42C1\"\n          },\n          \"title\": {\n            \"color\": \"#0433FF\"\n          },\n          \"type\": {\n            \"color\": \"#6f42c1\"\n          },\n          \"property\": {\n            \"color\": \"#333333\"\n          },\n          \"variable\": {\n            \"color\": \"#333333\"\n          },\n          \"variable.special\": {\n            \"color\": \"#C5060B\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"macOS Classic Dark\",\n      \"mode\": \"dark\",\n      \"shadow\": false,\n      \"colors\": {\n        \"accent.background\": \"#282629\",\n        \"accent.foreground\": \"#CACCCA\",\n        \"background\": \"#131313\",\n        \"border\": \"#303030\",\n        \"ring\": \"#077CFD\",\n        \"foreground\": \"#DEDEDE\",\n        \"list.even.background\": \"#232323\",\n        \"list.active.background\": \"#0059D115\",\n        \"list.active.border\": \"#077CFD\",\n        \"muted.background\": \"#202020\",\n        \"muted.foreground\": \"#9D9D9D\",\n        \"popover.background\": \"#101010\",\n        \"popover.foreground\": \"#CACCCA\",\n        \"primary.background\": \"#077CFD\",\n        \"primary.foreground\": \"#F2F9FF\",\n        \"switch.background\": \"#393939\",\n        \"switch.thumb.background\": \"#DAECFF\",\n        \"scrollbar.background\": \"#13131300\",\n        \"scrollbar.thumb.background\": \"#9F9F9F\",\n        \"secondary.active.background\": \"#353535\",\n        \"secondary.background\": \"#353535\",\n        \"secondary.foreground\": \"#DEDEDE\",\n        \"secondary.hover.background\": \"#35353599\",\n        \"link\": \"#419CFF\",\n        \"tab_bar.background\": \"#1C1C1E\",\n        \"tab.active.background\": \"#131313\",\n        \"tab.foreground\": \"#8F8F8F\",\n        \"title_bar.background\": \"#1C1C1E\",\n        \"selection.background\": \"#3F638B\",\n        \"base.blue\": \"#419CFF\",\n        \"base.cyan\": \"#07FDD3\",\n        \"base.green\": \"#30D158\",\n        \"base.magenta\": \"#A550A7\",\n        \"base.red\": \"#FF5257\",\n        \"base.yellow\": \"#FFC600\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#CACCCA\",\n        \"editor.background\": \"#131313\",\n        \"editor.active_line.background\": \"#35343666\",\n        \"editor.line_number\": \"#8F8F8F\",\n        \"editor.active_line_number\": \"#DDDDDD\",\n        \"editor.invisible\": \"#007fff66\",\n        \"conflict\": \"#D2602D\",\n        \"created\": \"#3f72e2\",\n        \"created.background\": \"#0C4619\",\n        \"deleted.background\": \"#46190C\",\n        \"error.background\": \"#46190C\",\n        \"error.border\": \"#802207\",\n        \"hidden\": \"#9E9E9E\",\n        \"hint\": \"#b283f8\",\n        \"hint.background\": \"#250c4b\",\n        \"hint.border\": \"#3f0891\",\n        \"info.background\": \"#0059D1\",\n        \"info.border\": \"#0059D1\",\n        \"modified\": \"#B0A878\",\n        \"modified.background\": \"#3A310E\",\n        \"predictive\": \"#5D5945\",\n        \"success.background\": \"#0C4619\",\n        \"warning.background\": \"#3A310E\",\n        \"warning.border\": \"#7B6508\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#e7cb8f\"\n          },\n          \"boolean\": {\n            \"color\": \"#E1D797\"\n          },\n          \"comment\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"constant\": {\n            \"color\": \"#E1D797\"\n          },\n          \"constructor\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"embedded\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"function\": {\n            \"color\": \"#fdd888\"\n          },\n          \"keyword\": {\n            \"color\": \"#c28b12\"\n          },\n          \"link_text\": {\n            \"color\": \"#307BF6\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#7faef9\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string\": {\n            \"color\": \"#62BA46\"\n          },\n          \"string.escape\": {\n            \"color\": \"#62BA46\"\n          },\n          \"string.regex\": {\n            \"color\": \"#62BA46\"\n          },\n          \"string.special\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#E1D797\"\n          },\n          \"tag\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"text.literal\": {\n            \"color\": \"#E1D797\"\n          },\n          \"title\": {\n            \"color\": \"#fdd888\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#c75828\"\n          },\n          \"property\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"variable.special\": {\n            \"color\": \"#E19773\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/matrix.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Matrix\",\n  \"author\": \"iruzo\",\n  \"url\": \"https://github.com/iruzo/matrix-nvim\",\n  \"themes\": [\n    {\n      \"name\": \"Matrix\",\n      \"mode\": \"dark\",\n      \"radius\": 0,\n      \"radius.lg\": 0,\n      \"colors\": {\n        \"accent.background\": \"#002d00\",\n        \"accent.foreground\": \"#00FF00\",\n        \"background\": \"#020D02\",\n        \"border\": \"#12410E\",\n        \"window_border\": \"#156B12\",\n        \"ring\": \"#00FF00\",\n        \"danger.background\": \"#FF0000\",\n        \"danger.active.background\": \"#FF0000\",\n        \"danger.foreground\": \"#0D0208\",\n        \"danger.hover.background\": \"#FF0000\",\n        \"foreground\": \"#88FF88\",\n        \"input.border\": \"#003200\",\n        \"link.active.foreground\": \"#00FF00\",\n        \"link.foreground\": \"#00FF00\",\n        \"link.hover.foreground\": \"#00FF00\",\n        \"list.active.background\": \"#00FF0011\",\n        \"list.active.border\": \"#00FF00\",\n        \"list.even.background\": \"#00190066\",\n        \"muted.background\": \"#001900\",\n        \"muted.foreground\": \"#007700\",\n        \"panel.background\": \"#001900\",\n        \"popover.background\": \"#020D02\",\n        \"popover.foreground\": \"#00FF00\",\n        \"primary.active.background\": \"#00FF0099\",\n        \"primary.background\": \"#00FF00\",\n        \"primary.foreground\": \"#0D0208\",\n        \"primary.hover.background\": \"#00FF00AA\",\n        \"scrollbar.background\": \"#0D020800\",\n        \"scrollbar.thumb.background\": \"#003d00\",\n        \"secondary.active.background\": \"#001B00\",\n        \"secondary.background\": \"#002900\",\n        \"secondary.foreground\": \"#3fd43a\",\n        \"secondary.hover.background\": \"#003300\",\n        \"tab.active.foreground\": \"#00FF00\",\n        \"tab.background\": \"#0D020800\",\n        \"tab.foreground\": \"#88FF88\",\n        \"title_bar.background\": \"#020d02\",\n        \"title_bar.border\": \"#12410E\",\n        \"base.red\": \"#FF0000\",\n        \"base.green\": \"#00FF00\",\n        \"base.yellow\": \"#ffea00\",\n        \"base.blue\": \"#0000ff\",\n        \"base.magenta\": \"#FF00FF\",\n        \"base.cyan\": \"#00ffd5\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#88FF88\",\n        \"editor.background\": \"#020d02\",\n        \"editor.active_line.background\": \"#001900\",\n        \"editor.line_number\": \"#007700\",\n        \"editor.active_line_number\": \"#00FF00\",\n        \"editor.invisible\": \"#00770066\",\n        \"conflict\": \"#FF0000\",\n        \"created\": \"#82d967\",\n        \"deleted\": \"#FF0000\",\n        \"error\": \"#FF0000\",\n        \"hidden\": \"#007700\",\n        \"hint\": \"#3fd43a\",\n        \"ignored\": \"#007700\",\n        \"info\": \"#3fd43a\",\n        \"modified\": \"#82d967\",\n        \"predictive\": \"#007700\",\n        \"renamed\": \"#82d967\",\n        \"success\": \"#82d967\",\n        \"unreachable\": \"#007700\",\n        \"warning\": \"#ffd700\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#82d967\"\n          },\n          \"boolean\": {\n            \"color\": \"#ffd700\"\n          },\n          \"comment\": {\n            \"color\": \"#007700\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#007700\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#ffd700\"\n          },\n          \"constructor\": {\n            \"color\": \"#82d967\"\n          },\n          \"embedded\": {\n            \"color\": \"#88FF88\"\n          },\n          \"function\": {\n            \"color\": \"#ffd700\"\n          },\n          \"keyword\": {\n            \"color\": \"#FF0000\"\n          },\n          \"link_text\": {\n            \"color\": \"#3fd43a\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#00FF00\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#FF0000\"\n          },\n          \"string\": {\n            \"color\": \"#82d967\"\n          },\n          \"string.escape\": {\n            \"color\": \"#82d967\"\n          },\n          \"string.regex\": {\n            \"color\": \"#82d967\"\n          },\n          \"string.special\": {\n            \"color\": \"#ffd700\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#ffd700\"\n          },\n          \"tag\": {\n            \"color\": \"#82d967\"\n          },\n          \"text.literal\": {\n            \"color\": \"#ffd700\"\n          },\n          \"title\": {\n            \"color\": \"#82d967\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#82d967\"\n          },\n          \"property\": {\n            \"color\": \"#88FF88\"\n          },\n          \"variable.special\": {\n            \"color\": \"#FF0000\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/mellifluous.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Mellifluous\",\n  \"author\": \"Ramojus Lapinskas\",\n  \"url\": \"https://github.com/ramojus/mellifluous.nvim\",\n  \"themes\": [\n    {\n      \"name\": \"Mellifluous Light\",\n      \"mode\": \"light\",\n      \"shadow\": false,\n      \"colors\": {\n        \"accent.background\": \"#d4d4d4\",\n        \"accent.foreground\": \"#383a42\",\n        \"background\": \"#E7E7E7\",\n        \"border\": \"#CACACA\",\n        \"danger.active.background\": \"#e06c75\",\n        \"danger.hover.background\": \"#be5046\",\n        \"foreground\": \"#383a42\",\n        \"link.active.foreground\": \"#5A6599\",\n        \"link.foreground\": \"#5A6599\",\n        \"link.hover.foreground\": \"#5A6599\",\n        \"list.hover.background\": \"#d5d5d5\",\n        \"list.active.background\": \"#5A659912\",\n        \"list.active.border\": \"#5A6599\",\n        \"list.even.background\": \"#F0F0F0\",\n        \"muted.background\": \"#E0E0E0\",\n        \"muted.foreground\": \"#828997\",\n        \"panel.background\": \"#fafafa\",\n        \"popover.background\": \"#E4E4E4\",\n        \"popover.foreground\": \"#383a42\",\n        \"primary.active.background\": \"#5A6599AA\",\n        \"primary.background\": \"#5A6599\",\n        \"primary.foreground\": \"#F0F0F0\",\n        \"primary.hover.background\": \"#626ca2\",\n        \"scrollbar.background\": \"#fafafa00\",\n        \"scrollbar.thumb.background\": \"#c0c0c0\",\n        \"secondary.background\": \"#d0d0d0\",\n        \"secondary.active.background\": \"#cccccc\",\n        \"secondary.foreground\": \"#383a42\",\n        \"secondary.hover.background\": \"#d7d7d7\",\n        \"tab.active.background\": \"#E7E7E7\",\n        \"tab.active.foreground\": \"#383a42\",\n        \"tab.background\": \"#F0F0F000\",\n        \"tab.foreground\": \"#727272\",\n        \"tab_bar.background\": \"#E7E7E7\",\n        \"title_bar.background\": \"#dfdfdf\",\n        \"title_bar.border\": \"#CACACA\",\n        \"base.red\": \"#C95954\",\n        \"base.green\": \"#828040\",\n        \"base.yellow\": \"#c98f54\",\n        \"base.blue\": \"#a8a1be\",\n        \"base.magenta\": \"#b39fb0\",\n        \"base.magenta.light\": \"#9C699588\",\n        \"base.cyan\": \"#54c981\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#383a42\",\n        \"editor.background\": \"#E7E7E7\",\n        \"editor.active_line.background\": \"#d5d5d5\",\n        \"editor.line_number\": \"#727272\",\n        \"editor.active_line_number\": \"#383a42\",\n        \"editor.invisible\": \"#A0A0A066\",\n        \"conflict\": \"#e06c75\",\n        \"created\": \"#828040\",\n        \"deleted\": \"#be5046\",\n        \"error\": \"#e06c75\",\n        \"hidden\": \"#727272\",\n        \"hint\": \"#5A6599\",\n        \"ignored\": \"#727272\",\n        \"info\": \"#5A6599\",\n        \"modified\": \"#727272\",\n        \"predictive\": \"#727272\",\n        \"renamed\": \"#727272\",\n        \"success\": \"#828040\",\n        \"unreachable\": \"#727272\",\n        \"warning\": \"#cbaa89\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#727272\"\n          },\n          \"boolean\": {\n            \"color\": \"#cbaa89\"\n          },\n          \"comment\": {\n            \"color\": \"#A0A0A0\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#A0A0A0\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#cbaa89\"\n          },\n          \"constructor\": {\n            \"color\": \"#727272\"\n          },\n          \"embedded\": {\n            \"color\": \"#383a42\"\n          },\n          \"function\": {\n            \"color\": \"#cbaa89\"\n          },\n          \"keyword\": {\n            \"color\": \"#e06c75\"\n          },\n          \"link_text\": {\n            \"color\": \"#5A6599\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#5A6599\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#e06c75\"\n          },\n          \"string\": {\n            \"color\": \"#828040\"\n          },\n          \"string.escape\": {\n            \"color\": \"#828040\"\n          },\n          \"string.regex\": {\n            \"color\": \"#828040\"\n          },\n          \"string.special\": {\n            \"color\": \"#cbaa89\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#cbaa89\"\n          },\n          \"tag\": {\n            \"color\": \"#727272\"\n          },\n          \"text.literal\": {\n            \"color\": \"#cbaa89\"\n          },\n          \"title\": {\n            \"color\": \"#727272\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#727272\"\n          },\n          \"property\": {\n            \"color\": \"#383a42\"\n          },\n          \"variable.special\": {\n            \"color\": \"#e06c75\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Mellifluous Dark\",\n      \"mode\": \"dark\",\n      \"shadow\": false,\n      \"colors\": {\n        \"accent.background\": \"#2f2f2f\",\n        \"accent.foreground\": \"#abb2bf\",\n        \"background\": \"#1A1A1A\",\n        \"border\": \"#444444\",\n        \"foreground\": \"#abb2bf\",\n        \"input.border\": \"#444444\",\n        \"link.active.foreground\": \"#5A6599\",\n        \"link.foreground\": \"#5A6599\",\n        \"link.hover.foreground\": \"#5A6599\",\n        \"list.active.background\": \"#5A659922\",\n        \"list.active.border\": \"#5A6599\",\n        \"list.even.background\": \"#29292988\",\n        \"muted.background\": \"#44444455\",\n        \"muted.foreground\": \"#828997\",\n        \"panel.background\": \"#282c34\",\n        \"popover.background\": \"#1A1A1A\",\n        \"popover.foreground\": \"#abb2bf\",\n        \"primary.active.background\": \"#5A6599AA\",\n        \"primary.background\": \"#5A6599\",\n        \"primary.foreground\": \"#ecedf4\",\n        \"primary.hover.background\": \"#626ca2\",\n        \"scrollbar.background\": \"#282c3400\",\n        \"scrollbar.thumb.background\": \"#444444\",\n        \"secondary.background\": \"#292929\",\n        \"secondary.active.background\": \"#323232\",\n        \"secondary.foreground\": \"#abb2bf\",\n        \"secondary.hover.background\": \"#292929\",\n        \"tab.active.background\": \"#1A1A1A\",\n        \"tab.active.foreground\": \"#abb2bf\",\n        \"tab.background\": \"#1A1A1A00\",\n        \"tab.foreground\": \"#abb2bf\",\n        \"title_bar.background\": \"#171717\",\n        \"title_bar.border\": \"#444444\",\n        \"base.red\": \"#C95954\",\n        \"base.green\": \"#828040\",\n        \"base.yellow\": \"#c98d54\",\n        \"base.blue\": \"#5481c9\",\n        \"base.magenta\": \"#9C6995\",\n        \"base.magenta.light\": \"#9C699577\",\n        \"base.cyan\": \"#54c9bd\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#abb2bf\",\n        \"editor.background\": \"#1A1A1A\",\n        \"editor.active_line.background\": \"#29292977\",\n        \"editor.line_number\": \"#828997\",\n        \"editor.active_line_number\": \"#abb2bf\",\n        \"editor.invisible\": \"#A0A0A066\",\n        \"conflict\": \"#e06c75\",\n        \"conflict.background\": \"#1A1A1A\",\n        \"conflict.border\": \"#be5046\",\n        \"created\": \"#828040\",\n        \"created.background\": \"#292929\",\n        \"created.border\": \"#929292\",\n        \"deleted\": \"#be5046\",\n        \"deleted.background\": \"#1A1A1A\",\n        \"deleted.border\": \"#e06c75\",\n        \"error\": \"#e06c75\",\n        \"error.background\": \"#1A1A1A\",\n        \"error.border\": \"#be5046\",\n        \"hidden\": \"#828997\",\n        \"hidden.background\": \"#292929\",\n        \"hidden.border\": \"#444444\",\n        \"hint\": \"#5A6599\",\n        \"hint.background\": \"#292929\",\n        \"hint.border\": \"#626ca2\",\n        \"ignored\": \"#828997\",\n        \"ignored.background\": \"#292929\",\n        \"ignored.border\": \"#444444\",\n        \"info\": \"#5A6599\",\n        \"info.background\": \"#292929\",\n        \"info.border\": \"#626ca2\",\n        \"modified\": \"#929292\",\n        \"modified.background\": \"#292929\",\n        \"modified.border\": \"#444444\",\n        \"predictive\": \"#828997\",\n        \"predictive.background\": \"#292929\",\n        \"predictive.border\": \"#444444\",\n        \"renamed\": \"#929292\",\n        \"renamed.background\": \"#292929\",\n        \"renamed.border\": \"#444444\",\n        \"success\": \"#828040\",\n        \"success.background\": \"#292929\",\n        \"success.border\": \"#929292\",\n        \"unreachable\": \"#828997\",\n        \"unreachable.background\": \"#292929\",\n        \"unreachable.border\": \"#444444\",\n        \"warning\": \"#d19a66\",\n        \"warning.background\": \"#292929\",\n        \"warning.border\": \"#929292\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#929292\"\n          },\n          \"boolean\": {\n            \"color\": \"#d19a66\"\n          },\n          \"comment\": {\n            \"color\": \"#828997\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#828997\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#d19a66\"\n          },\n          \"constructor\": {\n            \"color\": \"#929292\"\n          },\n          \"embedded\": {\n            \"color\": \"#abb2bf\"\n          },\n          \"function\": {\n            \"color\": \"#d19a66\"\n          },\n          \"keyword\": {\n            \"color\": \"#e06c75\"\n          },\n          \"link_text\": {\n            \"color\": \"#5A6599\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#5A6599\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#e06c75\"\n          },\n          \"string\": {\n            \"color\": \"#828040\"\n          },\n          \"string.escape\": {\n            \"color\": \"#828040\"\n          },\n          \"string.regex\": {\n            \"color\": \"#828040\"\n          },\n          \"string.special\": {\n            \"color\": \"#d19a66\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#d19a66\"\n          },\n          \"tag\": {\n            \"color\": \"#929292\"\n          },\n          \"text.literal\": {\n            \"color\": \"#d19a66\"\n          },\n          \"title\": {\n            \"color\": \"#929292\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#929292\"\n          },\n          \"property\": {\n            \"color\": \"#abb2bf\"\n          },\n          \"variable.special\": {\n            \"color\": \"#e06c75\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/molokai.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Molokai\",\n  \"author\": \"Tomas Restrepo\",\n  \"url\": \"https://github.com/tomasr/molokai\",\n  \"themes\": [\n    {\n      \"name\": \"Molokai Light\",\n      \"mode\": \"light\",\n      \"shadow\": false,\n      \"colors\": {\n        \"accent.background\": \"#EBE4E1\",\n        \"accent.foreground\": \"#060606\",\n        \"background\": \"#FEFAF9\",\n        \"border\": \"#E4DEDA\",\n        \"window_border\": \"#d6d6d6\",\n        \"ring\": \"#a0a0ab\",\n        \"foreground\": \"#0a0a0a\",\n        \"input.border\": \"#E4DEDA\",\n        \"list.active.background\": \"#E1477411\",\n        \"list.active.border\": \"#E14774AA\",\n        \"list.even.background\": \"#E4DEDA33\",\n        \"muted.background\": \"#f1eded\",\n        \"muted.foreground\": \"#767676\",\n        \"panel.background\": \"#FEFAF9\",\n        \"popover.background\": \"#FEFAF9\",\n        \"popover.foreground\": \"#0a0a0a\",\n        \"primary.background\": \"#E14774\",\n        \"primary.foreground\": \"#fafafa\",\n        \"scrollbar.background\": \"#FEFAF900\",\n        \"scrollbar.thumb.background\": \"#ADADB0\",\n        \"secondary.background\": \"#E4DEDA\",\n        \"secondary.foreground\": \"#060606\",\n        \"secondary.hover.background\": \"#E4DEDA99\",\n        \"title_bar.background\": \"#e8e3e0\",\n        \"title_bar.border\": \"#d5ccc6\",\n        \"base.red\": \"#e17047\",\n        \"base.green\": \"#82cc0a\",\n        \"base.yellow\": \"#ccac0a\",\n        \"base.blue\": \"#e16032\",\n        \"base.blue.light\": \"#e16032\",\n        \"base.magenta\": \"#7058be\",\n        \"base.cyan\": \"#0acc78\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#0a0a0a\",\n        \"editor.background\": \"#FEFAF9\",\n        \"editor.active_line.background\": \"#E4DEDA\",\n        \"editor.line_number\": \"#767676\",\n        \"editor.active_line_number\": \"#0a0a0a\",\n        \"editor.invisible\": \"#76767666\",\n        \"conflict\": \"#e14775\",\n        \"created\": \"#269D69\",\n        \"deleted\": \"#e14775\",\n        \"error\": \"#e14775\",\n        \"hidden\": \"#767676\",\n        \"hint\": \"#7058be\",\n        \"ignored\": \"#767676\",\n        \"info\": \"#1c8ca8\",\n        \"modified\": \"#cc7a0a\",\n        \"predictive\": \"#ADADB0\",\n        \"renamed\": \"#E14774\",\n        \"success\": \"#269D69\",\n        \"unreachable\": \"#767676\",\n        \"warning\": \"#cc7a0a\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#e14775\"\n          },\n          \"boolean\": {\n            \"color\": \"#cc7a0a\"\n          },\n          \"comment\": {\n            \"color\": \"#767676\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#767676\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#7058be\"\n          },\n          \"constructor\": {\n            \"color\": \"#269D69\"\n          },\n          \"embedded\": {\n            \"color\": \"#1c8ca8\"\n          },\n          \"function\": {\n            \"color\": \"#e14775\"\n          },\n          \"keyword\": {\n            \"color\": \"#e14775\",\n            \"font_style\": \"normal\"\n          },\n          \"link_text\": {\n            \"color\": \"#cc7a0a\",\n            \"font_style\": \"underline\"\n          },\n          \"link_uri\": {\n            \"color\": \"#7058be\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#cc7a0a\"\n          },\n          \"string\": {\n            \"color\": \"#269D69\"\n          },\n          \"string.escape\": {\n            \"color\": \"#7058be\"\n          },\n          \"string.regex\": {\n            \"color\": \"#269D69\"\n          },\n          \"string.special\": {\n            \"color\": \"#e14775\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#e14775\"\n          },\n          \"tag\": {\n            \"color\": \"#e14775\"\n          },\n          \"text.literal\": {\n            \"color\": \"#7058be\"\n          },\n          \"title\": {\n            \"color\": \"#cc7a0a\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#1c8ca8\"\n          },\n          \"property\": {\n            \"color\": \"#7058be\"\n          },\n          \"variable.special\": {\n            \"color\": \"#e14775\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Molokai Dark\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#2b2f30\",\n        \"accent.foreground\": \"#f8f8f2\",\n        \"background\": \"#1b1d1e\",\n        \"border\": \"#393939\",\n        \"foreground\": \"#f8f8f2\",\n        \"input.border\": \"#3b3b3b\",\n        \"list.active.background\": \"#f9267211\",\n        \"list.active.border\": \"#f9267288\",\n        \"list.even.background\": \"#292c2e\",\n        \"muted.background\": \"#232526\",\n        \"muted.foreground\": \"#5b5a54\",\n        \"panel.background\": \"#1b1d1e\",\n        \"popover.background\": \"#1b1d1e\",\n        \"popover.foreground\": \"#f8f8f2\",\n        \"primary.background\": \"#dc1860\",\n        \"primary.foreground\": \"#f8f8f2\",\n        \"scrollbar.background\": \"#1b1d1e00\",\n        \"scrollbar.thumb.background\": \"#4b4b4b\",\n        \"secondary.background\": \"#292c2e\",\n        \"secondary.active.background\": \"#303436\",\n        \"secondary.foreground\": \"#f5f5f4\",\n        \"secondary.hover.background\": \"#30343699\",\n        \"table.row.border\": \"#3b3b3b70\",\n        \"title_bar.background\": \"#202223\",\n        \"title_bar.border\": \"#3b3b3b\",\n        \"base.red\": \"#dc1860\",\n        \"base.green\": \"#82cc0a\",\n        \"base.yellow\": \"#807607\",\n        \"base.blue\": \"#075880\",\n        \"base.magenta\": \"#800780\",\n        \"base.cyan\": \"#07807e\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#DDDDDD\",\n        \"editor.background\": \"#000000\",\n        \"editor.active_line.background\": \"#131313\",\n        \"editor.line_number\": \"#8F8F8F\",\n        \"editor.active_line_number\": \"#DDDDDD\",\n        \"editor.invisible\": \"#76767666\",\n        \"conflict\": \"#D2602D\",\n        \"created\": \"#3f72e2\",\n        \"created.background\": \"#0C4619\",\n        \"deleted.background\": \"#46190C\",\n        \"error.background\": \"#46190C\",\n        \"error.border\": \"#802207\",\n        \"hidden\": \"#9E9E9E\",\n        \"hint\": \"#b283f8\",\n        \"hint.background\": \"#250c4b\",\n        \"hint.border\": \"#3f0891\",\n        \"info.background\": \"#0C194D\",\n        \"info.border\": \"#082190\",\n        \"modified\": \"#B0A878\",\n        \"modified.background\": \"#3A310E\",\n        \"predictive\": \"#5D5945\",\n        \"success.background\": \"#0C4619\",\n        \"warning.background\": \"#3A310E\",\n        \"warning.border\": \"#7B6508\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#be9a52\"\n          },\n          \"boolean\": {\n            \"color\": \"#E1D797\"\n          },\n          \"comment\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"constant\": {\n            \"color\": \"#E1D797\"\n          },\n          \"constructor\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"embedded\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"function\": {\n            \"color\": \"#E1D797\"\n          },\n          \"keyword\": {\n            \"color\": \"#E19773\"\n          },\n          \"link_text\": {\n            \"color\": \"#A86D3B\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#6F6D66\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#E19773\"\n          },\n          \"string\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.escape\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.regex\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.special\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#E1D797\"\n          },\n          \"tag\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"text.literal\": {\n            \"color\": \"#E1D797\"\n          },\n          \"title\": {\n            \"color\": \"#A76D3B\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#A86D3B\"\n          },\n          \"property\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"variable.special\": {\n            \"color\": \"#E19773\"\n          }\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "themes/solarized.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Solarized\",\n  \"author\": \"Ethan Schoonover\",\n  \"url\": \"https://ethanschoonover.com/solarized\",\n  \"themes\": [\n    {\n      \"name\": \"Solarized Light\",\n      \"mode\": \"light\",\n      \"colors\": {\n        \"accent.background\": \"#ebe4ce\",\n        \"accent.foreground\": \"#073642\",\n        \"background\": \"#FDF6E3\",\n        \"border\": \"#DCD4BC\",\n        \"foreground\": \"#586E75\",\n        \"input.border\": \"#DCD4BC\",\n        \"link.foreground\": \"#587573\",\n        \"list.active.background\": \"#586E75\",\n        \"list.active.border\": \"#586E75\",\n        \"list.even.background\": \"#EEE8D599\",\n        \"muted.background\": \"#eee8d5\",\n        \"muted.foreground\": \"#93a1a1\",\n        \"panel.background\": \"#fdf6e3\",\n        \"popover.background\": \"#fdf6e3\",\n        \"popover.foreground\": \"#073642\",\n        \"primary.background\": \"#586E75\",\n        \"primary.foreground\": \"#fdf6e3\",\n        \"scrollbar.background\": \"#EEE8D500\",\n        \"scrollbar.thumb.background\": \"#d5cbaf\",\n        \"secondary.background\": \"#EEE8D5\",\n        \"secondary.active.background\": \"#DCD4BC\",\n        \"secondary.foreground\": \"#073642\",\n        \"secondary.hover.background\": \"#DCD4BC88\",\n        \"tab.active.background\": \"#fdf6e3\",\n        \"tab.active.foreground\": \"#073642\",\n        \"tab.background\": \"#fdf6e300\",\n        \"tab.foreground\": \"#657b83\",\n        \"tab_bar.background\": \"#eee8d5\",\n        \"title_bar.background\": \"#f4ecd7\",\n        \"title_bar.border\": \"#dcd4bc\",\n        \"chart.grid\": \"#ebe5d4\",\n        \"base.red\": \"#755858\",\n        \"base.green\": \"#717558\",\n        \"base.yellow\": \"#756e58\",\n        \"base.blue\": \"#586975\",\n        \"base.magenta\": \"#755866\",\n        \"base.cyan\": \"#587573\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#586E75\",\n        \"editor.background\": \"#FDF6E3\",\n        \"editor.active_line.background\": \"#EEE8D5\",\n        \"editor.line_number\": \"#93A1A1\",\n        \"editor.active_line_number\": \"#073642\",\n        \"editor.invisible\": \"#93A1A166\",\n        \"conflict\": \"#DC322F\",\n        \"created\": \"#859900\",\n        \"deleted\": \"#DC322F\",\n        \"error\": \"#DC322F\",\n        \"hidden\": \"#93A1A1\",\n        \"hint\": \"#2AA198\",\n        \"ignored\": \"#93A1A1\",\n        \"info\": \"#268BD2\",\n        \"modified\": \"#B58900\",\n        \"predictive\": \"#586E75\",\n        \"renamed\": \"#2AA198\",\n        \"success\": \"#859900\",\n        \"unreachable\": \"#DC322F\",\n        \"warning\": \"#B58900\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#B58900\"\n          },\n          \"boolean\": {\n            \"color\": \"#CB4B16\"\n          },\n          \"comment\": {\n            \"color\": \"#93A1A1\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#93A1A1\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#268BD2\"\n          },\n          \"constructor\": {\n            \"color\": \"#859900\"\n          },\n          \"embedded\": {\n            \"color\": \"#2AA198\"\n          },\n          \"function\": {\n            \"color\": \"#268BD2\"\n          },\n          \"keyword\": {\n            \"color\": \"#DC322F\"\n          },\n          \"link_text\": {\n            \"color\": \"#268BD2\",\n            \"font_style\": \"underline\"\n          },\n          \"link_uri\": {\n            \"color\": \"#2AA198\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#D33682\"\n          },\n          \"string\": {\n            \"color\": \"#859900\"\n          },\n          \"string.escape\": {\n            \"color\": \"#2AA198\"\n          },\n          \"string.regex\": {\n            \"color\": \"#D33682\"\n          },\n          \"string.special\": {\n            \"color\": \"#B58900\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#B58900\"\n          },\n          \"tag\": {\n            \"color\": \"#268BD2\"\n          },\n          \"text.literal\": {\n            \"color\": \"#586E75\"\n          },\n          \"title\": {\n            \"color\": \"#073642\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#2AA198\"\n          },\n          \"property\": {\n            \"color\": \"#586E75\"\n          },\n          \"variable.special\": {\n            \"color\": \"#DC322F\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Solarized Dark\",\n      \"author\": \"Ethan Schoonover\",\n      \"url\": \"https://ethanschoonover.com/solarized\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#073945\",\n        \"accent.foreground\": \"#fdf6e3\",\n        \"background\": \"#002B36\",\n        \"border\": \"#103a44\",\n        \"foreground\": \"#fdf6e3\",\n        \"input.border\": \"#083e4c\",\n        \"list.active.background\": \"#0d667d25\",\n        \"list.active.border\": \"#0d667d\",\n        \"list.even.background\": \"#15333A99\",\n        \"muted.background\": \"#073642\",\n        \"muted.foreground\": \"#839496\",\n        \"panel.background\": \"#002b36\",\n        \"popover.background\": \"#002b36\",\n        \"popover.foreground\": \"#fdf6e3\",\n        \"primary.background\": \"#0d667d\",\n        \"primary.foreground\": \"#fdf6e3\",\n        \"scrollbar.background\": \"#002b3600\",\n        \"scrollbar.thumb.background\": \"#586e75\",\n        \"secondary.background\": \"#073642\",\n        \"secondary.active.background\": \"#083e4b\",\n        \"secondary.foreground\": \"#fdf6e3\",\n        \"secondary.hover.background\": \"#073945\",\n        \"tab.active.background\": \"#002b36\",\n        \"tab.active.foreground\": \"#fdf6e3\",\n        \"tab.foreground\": \"#93a1a1\",\n        \"tab_bar.background\": \"#07364266\",\n        \"title_bar.background\": \"#09323d\",\n        \"title_bar.border\": \"#16404b\",\n        \"base.red\": \"#7d2f0d\",\n        \"base.green\": \"#0d7d3f\",\n        \"base.yellow\": \"#7d650d\",\n        \"base.blue\": \"#0d507d\",\n        \"base.magenta\": \"#7d0d43\",\n        \"base.cyan\": \"#0d7d70\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#DDDDDD\",\n        \"editor.background\": \"#002B36\",\n        \"editor.active_line.background\": \"#073642\",\n        \"editor.line_number\": \"#8F8F8F\",\n        \"editor.active_line_number\": \"#DDDDDD\",\n        \"editor.invisible\": \"#93A1A166\",\n        \"conflict\": \"#D2602D\",\n        \"created\": \"#3f72e2\",\n        \"created.background\": \"#0C4619\",\n        \"deleted.background\": \"#46190C\",\n        \"error.background\": \"#46190C\",\n        \"error.border\": \"#802207\",\n        \"hidden\": \"#9E9E9E\",\n        \"hint\": \"#b283f8\",\n        \"hint.background\": \"#250c4b\",\n        \"hint.border\": \"#3f0891\",\n        \"info.background\": \"#0C194D\",\n        \"info.border\": \"#082190\",\n        \"modified\": \"#B0A878\",\n        \"modified.background\": \"#3A310E\",\n        \"predictive\": \"#5D5945\",\n        \"success.background\": \"#0C4619\",\n        \"warning.background\": \"#3A310E\",\n        \"warning.border\": \"#7B6508\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#be9a52\"\n          },\n          \"boolean\": {\n            \"color\": \"#E1D797\"\n          },\n          \"comment\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"constant\": {\n            \"color\": \"#E1D797\"\n          },\n          \"constructor\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"embedded\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"function\": {\n            \"color\": \"#E1D797\"\n          },\n          \"keyword\": {\n            \"color\": \"#E19773\"\n          },\n          \"link_text\": {\n            \"color\": \"#A86D3B\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#6F6D66\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#E19773\"\n          },\n          \"string\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.escape\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.regex\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.special\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#E1D797\"\n          },\n          \"tag\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"text.literal\": {\n            \"color\": \"#E1D797\"\n          },\n          \"title\": {\n            \"color\": \"#A76D3B\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#A86D3B\"\n          },\n          \"property\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"variable.special\": {\n            \"color\": \"#E19773\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/spaceduck.json",
    "content": "{\n  \"name\": \"Spaceduck\",\n  \"author\": \"Guillermo Rodriguez\",\n  \"url\": \"https://github.com/pineapplegiant/spaceduck\",\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"themes\": [\n    {\n      \"name\": \"Spaceduck\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#212440\",\n        \"accent.foreground\": \"#a3a49d\",\n        \"background\": \"#0F111B\",\n        \"border\": \"#272C42\",\n        \"foreground\": \"#a3a49d\",\n        \"input.border\": \"#272C42\",\n        \"link.active.foreground\": \"#089CC5\",\n        \"link.foreground\": \"#089CC5\",\n        \"link.hover.foreground\": \"#089CC5\",\n        \"list.active.background\": \"#089CC511\",\n        \"list.active.border\": \"#089CC5\",\n        \"list.even.background\": \"#161928\",\n        \"muted.background\": \"#272C4255\",\n        \"muted.foreground\": \"#4b6479\",\n        \"panel.background\": \"#003a5b\",\n        \"popover.background\": \"#0F111B\",\n        \"popover.foreground\": \"#a3a49d\",\n        \"primary.background\": \"#089CC5\",\n        \"primary.foreground\": \"#ecf0c1\",\n        \"scrollbar.background\": \"#003a5b00\",\n        \"scrollbar.thumb.background\": \"#24303a\",\n        \"secondary.background\": \"#242547\",\n        \"secondary.active.background\": \"#292b51\",\n        \"secondary.foreground\": \"#a3a49d\",\n        \"secondary.hover.background\": \"#242547\",\n        \"tab.active.background\": \"#0F111B\",\n        \"tab.foreground\": \"#838182\",\n        \"title_bar.background\": \"#141724\",\n        \"title_bar.border\": \"#272C42\",\n        \"base.red\": \"#e33400\",\n        \"base.green\": \"#5ccc96\",\n        \"base.blue\": \"#00a3cc\",\n        \"base.cyan\": \"#089CC5\",\n        \"base.magenta\": \"#C86D8C\",\n        \"base.magenta.light\": \"#C86D8C33\",\n        \"base.yellow\": \"#b89c00\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#ecf0c1\",\n        \"editor.background\": \"#0F111B\",\n        \"editor.active_line.background\": \"#1c1d39\",\n        \"editor.line_number\": \"#4b6479\",\n        \"editor.active_line_number\": \"#ecf0c1\",\n        \"editor.invisible\": \"#4b647966\",\n        \"conflict\": \"#e33400\",\n        \"created\": \"#5ccc96\",\n        \"deleted\": \"#e33400\",\n        \"error\": \"#e33400\",\n        \"hidden\": \"#4b6479\",\n        \"hint\": \"#C86D8C\",\n        \"ignored\": \"#4b6479\",\n        \"info\": \"#089CC5\",\n        \"modified\": \"#f2ce00\",\n        \"predictive\": \"#5D5945\",\n        \"renamed\": \"#089CC5\",\n        \"success\": \"#5ccc96\",\n        \"unreachable\": \"#4b6479\",\n        \"warning\": \"#f2ce00\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#f2ce00\"\n          },\n          \"boolean\": {\n            \"color\": \"#f2ce00\"\n          },\n          \"comment\": {\n            \"color\": \"#4b6479\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#4b6479\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#e33400\"\n          },\n          \"constructor\": {\n            \"color\": \"#5ccc96\"\n          },\n          \"embedded\": {\n            \"color\": \"#ecf0c1\"\n          },\n          \"function\": {\n            \"color\": \"#089CC5\"\n          },\n          \"keyword\": {\n            \"color\": \"#C86D8C\"\n          },\n          \"link_text\": {\n            \"color\": \"#089CC5\",\n            \"font_style\": \"underline\"\n          },\n          \"link_uri\": {\n            \"color\": \"#089CC5\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#f2ce00\"\n          },\n          \"string\": {\n            \"color\": \"#5ccc96\"\n          },\n          \"string.escape\": {\n            \"color\": \"#5ccc96\"\n          },\n          \"string.regex\": {\n            \"color\": \"#5ccc96\"\n          },\n          \"string.special\": {\n            \"color\": \"#f2ce00\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#f2ce00\"\n          },\n          \"tag\": {\n            \"color\": \"#C86D8C\"\n          },\n          \"text.literal\": {\n            \"color\": \"#ecf0c1\"\n          },\n          \"title\": {\n            \"color\": \"#f2ce00\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#089CC5\"\n          },\n          \"property\": {\n            \"color\": \"#ecf0c1\"\n          },\n          \"variable.special\": {\n            \"color\": \"#C86D8C\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/tokyonight.json",
    "content": "{\n  \"name\": \"Tokyo\",\n  \"author\": \"Folke Lemaitre\",\n  \"url\": \"https://github.com/folke/tokyonight.nvim\",\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"themes\": [\n    {\n      \"name\": \"Tokyo Night\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#2C3045\",\n        \"accent.foreground\": \"#c0caf5\",\n        \"background\": \"#1a1b26\",\n        \"border\": \"#292e42\",\n        \"window_border\": \"#3d4462\",\n        \"ring\": \"#7aa2f7\",\n        \"foreground\": \"#c0caf5\",\n        \"link.active.foreground\": \"#7aa2f722\",\n        \"link.foreground\": \"#7aa2f7\",\n        \"link.hover.foreground\": \"#7aa2f7\",\n        \"list.active.background\": \"#7aa2f722\",\n        \"list.active.border\": \"#7aa2f7\",\n        \"list.even.background\": \"#2C304599\",\n        \"muted.background\": \"#2C304544\",\n        \"muted.foreground\": \"#565f89\",\n        \"panel.background\": \"#292e42\",\n        \"popover.background\": \"#1a1b26\",\n        \"popover.foreground\": \"#c0caf5\",\n        \"primary.background\": \"#7aa2f7\",\n        \"primary.foreground\": \"#1a1b26\",\n        \"scrollbar.background\": \"#1a1b2600\",\n        \"scrollbar.thumb.background\": \"#414868\",\n        \"secondary.active.background\": \"#292e42\",\n        \"secondary.background\": \"#292e42\",\n        \"secondary.foreground\": \"#C0CAF5\",\n        \"secondary.hover.background\": \"#2d3349\",\n        \"title_bar.background\": \"#161720\",\n        \"title_bar.border\": \"#292e42\",\n        \"chart.grid\": \"#222332\",\n        \"base.red\": \"#f7768e\",\n        \"base.green\": \"#9ece6a\",\n        \"base.yellow\": \"#e0af68\",\n        \"base.blue\": \"#7aa2f7\",\n        \"base.magenta\": \"#565f89\",\n        \"base.cyan\": \"#7dcfff\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#c0caf5\",\n        \"editor.background\": \"#1a1b26\",\n        \"editor.active_line.background\": \"#292e42\",\n        \"editor.line_number\": \"#565f89\",\n        \"editor.active_line_number\": \"#c0caf5\",\n        \"editor.invisible\": \"#565f8966\",\n        \"conflict\": \"#f7768e\",\n        \"created\": \"#9ece6a\",\n        \"deleted\": \"#f7768e\",\n        \"error\": \"#f7768e\",\n        \"hidden\": \"#565f89\",\n        \"hint\": \"#7dcfff\",\n        \"ignored\": \"#565f89\",\n        \"info\": \"#7aa2f7\",\n        \"modified\": \"#e0af68\",\n        \"predictive\": \"#565f89\",\n        \"renamed\": \"#7aa2f7\",\n        \"success\": \"#9ece6a\",\n        \"unreachable\": \"#565f89\",\n        \"warning\": \"#e0af68\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#e0af68\"\n          },\n          \"boolean\": {\n            \"color\": \"#9ece6a\"\n          },\n          \"comment\": {\n            \"color\": \"#565f89\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#565f89\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#7aa2f7\"\n          },\n          \"constructor\": {\n            \"color\": \"#e0af68\"\n          },\n          \"embedded\": {\n            \"color\": \"#c0caf5\"\n          },\n          \"function\": {\n            \"color\": \"#7aa2f7\"\n          },\n          \"keyword\": {\n            \"color\": \"#f7768e\"\n          },\n          \"link_text\": {\n            \"color\": \"#7dcfff\",\n            \"font_style\": \"underline\"\n          },\n          \"link_uri\": {\n            \"color\": \"#7aa2f7\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#e0af68\"\n          },\n          \"string\": {\n            \"color\": \"#9ece6a\"\n          },\n          \"string.escape\": {\n            \"color\": \"#7dcfff\"\n          },\n          \"string.regex\": {\n            \"color\": \"#9ece6a\"\n          },\n          \"string.special\": {\n            \"color\": \"#e0af68\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#e0af68\"\n          },\n          \"tag\": {\n            \"color\": \"#f7768e\"\n          },\n          \"text.literal\": {\n            \"color\": \"#c0caf5\"\n          },\n          \"title\": {\n            \"color\": \"#7aa2f7\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#7dcfff\"\n          },\n          \"property\": {\n            \"color\": \"#c0caf5\"\n          },\n          \"variable.special\": {\n            \"color\": \"#f7768e\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Tokyo Storm\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#414868\",\n        \"accent.foreground\": \"#c0caf5\",\n        \"background\": \"#24283b\",\n        \"border\": \"#3d4462\",\n        \"window_border\": \"#3d4462\",\n        \"ring\": \"#7aa2f7\",\n        \"foreground\": \"#c0caf5\",\n        \"input.border\": \"#414868\",\n        \"link.active.foreground\": \"#7aa2f7\",\n        \"link.foreground\": \"#7aa2f7\",\n        \"link.hover.foreground\": \"#7aa2f7\",\n        \"list.active.background\": \"#7aa2f711\",\n        \"list.active.border\": \"#7aa2f7\",\n        \"list.even.background\": \"#41486844\",\n        \"muted.background\": \"#292e42\",\n        \"muted.foreground\": \"#565f89\",\n        \"panel.background\": \"#292e42\",\n        \"popover.background\": \"#24283b\",\n        \"popover.foreground\": \"#c0caf5\",\n        \"primary.background\": \"#7aa2f7\",\n        \"primary.foreground\": \"#24283b\",\n        \"scrollbar.background\": \"#24283b00\",\n        \"scrollbar.thumb.background\": \"#414868\",\n        \"secondary.active.background\": \"#323750\",\n        \"secondary.background\": \"#292e42\",\n        \"secondary.foreground\": \"#c0caf5\",\n        \"secondary.hover.background\": \"#32375099\",\n        \"title_bar.background\": \"#202435\",\n        \"title_bar.border\": \"#3d4462\",\n        \"base.red\": \"#f7768e\",\n        \"base.green\": \"#9ece6a\",\n        \"base.yellow\": \"#e0af68\",\n        \"base.blue\": \"#7aa2f7\",\n        \"base.magenta\": \"#b283f8\",\n        \"base.cyan\": \"#7dcfff\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#B0B9E2\",\n        \"editor.background\": \"#24283B\",\n        \"editor.active_line.background\": \"#292E42\",\n        \"editor.line_number\": \"#363C58\",\n        \"editor.active_line_number\": \"#B0B9E2\",\n        \"editor.invisible\": \"#565f8966\",\n        \"conflict\": \"#D2602D\",\n        \"created\": \"#3f72e2\",\n        \"deleted\": \"#f7768e\",\n        \"error\": \"#f7768e\",\n        \"hidden\": \"#9E9E9E\",\n        \"hint\": \"#b283f8\",\n        \"modified\": \"#B0A878\",\n        \"predictive\": \"#5D5945\",\n        \"success\": \"#9ece6a\",\n        \"warning\": \"#e0af68\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#be9a52\"\n          },\n          \"boolean\": {\n            \"color\": \"#E1D797\"\n          },\n          \"comment\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"constant\": {\n            \"color\": \"#E1D797\"\n          },\n          \"constructor\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"embedded\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"function\": {\n            \"color\": \"#E1D797\"\n          },\n          \"keyword\": {\n            \"color\": \"#E19773\"\n          },\n          \"link_text\": {\n            \"color\": \"#A86D3B\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#6F6D66\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#E19773\"\n          },\n          \"string\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.escape\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.regex\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.special\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#E1D797\"\n          },\n          \"tag\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"text.literal\": {\n            \"color\": \"#E1D797\"\n          },\n          \"title\": {\n            \"color\": \"#A76D3B\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#A86D3B\"\n          },\n          \"property\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"variable.special\": {\n            \"color\": \"#E19773\"\n          }\n        }\n      }\n    },\n    {\n      \"name\": \"Tokyo Moon\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#32364E\",\n        \"accent.foreground\": \"#c0caf5\",\n        \"background\": \"#222436\",\n        \"border\": \"#323752\",\n        \"window_border\": \"#404668\",\n        \"ring\": \"#82aaff\",\n        \"foreground\": \"#c0caf5\",\n        \"input.border\": \"#444b6a\",\n        \"link.active.foreground\": \"#82aaff\",\n        \"link.foreground\": \"#82aaff\",\n        \"link.hover.foreground\": \"#82aaff\",\n        \"list.active.background\": \"#82aaff22\",\n        \"list.active.border\": \"#82aaff\",\n        \"list.background\": \"#222436\",\n        \"list.even.background\": \"#282a40\",\n        \"muted.background\": \"#2d3149\",\n        \"muted.foreground\": \"#6e738d\",\n        \"panel.background\": \"#2d3149\",\n        \"popover.background\": \"#222436\",\n        \"popover.foreground\": \"#c0caf5\",\n        \"primary.background\": \"#82aaff\",\n        \"primary.foreground\": \"#222436\",\n        \"scrollbar.background\": \"#22243600\",\n        \"scrollbar.thumb.background\": \"#444b6a\",\n        \"secondary.active.background\": \"#2f334c\",\n        \"secondary.background\": \"#2d3149\",\n        \"secondary.foreground\": \"#8c94b5\",\n        \"secondary.hover.background\": \"#31354f\",\n        \"table.row.border\": \"#2d3149\",\n        \"title_bar.background\": \"#1e2030\",\n        \"title_bar.border\": \"#323752\",\n        \"base.red\": \"#ff757f\",\n        \"base.green\": \"#c3e88d\",\n        \"base.yellow\": \"#ffc777\",\n        \"base.blue\": \"#82aaff\",\n        \"base.magenta\": \"#6e738d\",\n        \"base.cyan\": \"#86e1fc\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#c0caf5\",\n        \"editor.background\": \"#222436\",\n        \"editor.active_line.background\": \"#2d3149\",\n        \"editor.line_number\": \"#6e738d\",\n        \"editor.active_line_number\": \"#c0caf5\",\n        \"editor.invisible\": \"#565f8966\",\n        \"conflict\": \"#ed8796\",\n        \"created\": \"#c3e88d\",\n        \"deleted\": \"#ed8796\",\n        \"error\": \"#ed8796\",\n        \"hidden\": \"#6e738d\",\n        \"hint\": \"#86e1fc\",\n        \"ignored\": \"#6e738d\",\n        \"info\": \"#82aaff\",\n        \"modified\": \"#ffc777\",\n        \"predictive\": \"#6e738d\",\n        \"renamed\": \"#82aaff\",\n        \"success\": \"#c3e88d\",\n        \"unreachable\": \"#6e738d\",\n        \"warning\": \"#ffc777\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#ffc777\"\n          },\n          \"boolean\": {\n            \"color\": \"#c3e88d\"\n          },\n          \"comment\": {\n            \"color\": \"#6e738d\",\n            \"font_style\": \"italic\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#6e738d\",\n            \"font_style\": \"italic\"\n          },\n          \"constant\": {\n            \"color\": \"#82aaff\"\n          },\n          \"constructor\": {\n            \"color\": \"#ffc777\"\n          },\n          \"embedded\": {\n            \"color\": \"#c0caf5\"\n          },\n          \"function\": {\n            \"color\": \"#82aaff\"\n          },\n          \"keyword\": {\n            \"color\": \"#ed8796\"\n          },\n          \"link_text\": {\n            \"color\": \"#86e1fc\",\n            \"font_style\": \"underline\"\n          },\n          \"link_uri\": {\n            \"color\": \"#82aaff\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#ffc777\"\n          },\n          \"string\": {\n            \"color\": \"#c3e88d\"\n          },\n          \"string.escape\": {\n            \"color\": \"#86e1fc\"\n          },\n          \"string.regex\": {\n            \"color\": \"#c3e88d\"\n          },\n          \"string.special\": {\n            \"color\": \"#ffc777\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#ffc777\"\n          },\n          \"tag\": {\n            \"color\": \"#ed8796\"\n          },\n          \"text.literal\": {\n            \"color\": \"#c0caf5\"\n          },\n          \"title\": {\n            \"color\": \"#82aaff\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#86e1fc\"\n          },\n          \"property\": {\n            \"color\": \"#c0caf5\"\n          },\n          \"variable.special\": {\n            \"color\": \"#ed8796\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "themes/twilight.json",
    "content": "{\n  \"$schema\": \"https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json\",\n  \"name\": \"Twilight\",\n  \"author\": \"MacroMates\",\n  \"url\": \"https://macromates.com\",\n  \"themes\": [\n    {\n      \"name\": \"Twilight\",\n      \"mode\": \"dark\",\n      \"colors\": {\n        \"accent.background\": \"#2b2b2b\",\n        \"accent.foreground\": \"#dcdcdc\",\n        \"background\": \"#141414\",\n        \"border\": \"#343434\",\n        \"foreground\": \"#dcdcdc\",\n        \"input.border\": \"#3e3e3e\",\n        \"list.active.background\": \"#CDA86911\",\n        \"list.active.border\": \"#CDA86988\",\n        \"list.even.background\": \"#1E1E1E99\",\n        \"muted.background\": \"#2e2e2e88\",\n        \"muted.foreground\": \"#828282\",\n        \"primary.background\": \"#CDA869\",\n        \"primary.foreground\": \"#1e1e1e\",\n        \"scrollbar.background\": \"#1e1e1e00\",\n        \"scrollbar.thumb.background\": \"#3e3e3e\",\n        \"secondary.background\": \"#262626\",\n        \"secondary.active.background\": \"#2a2a2a\",\n        \"secondary.foreground\": \"#dcdcdc\",\n        \"secondary.hover.background\": \"#272727\",\n        \"tab.active.background\": \"#141414\",\n        \"tab.active.foreground\": \"#dcdcdc\",\n        \"tab.background\": \"#1E1E1E00\",\n        \"tab.foreground\": \"#A9A9A9\",\n        \"tab_bar.background\": \"#1E1E1E\",\n        \"table.background\": \"#1e1e1e00\",\n        \"title_bar.background\": \"#1e1e1e\",\n        \"title_bar.border\": \"#343434\",\n        \"base.red\": \"#c06d44\",\n        \"base.red.light\": \"#de7c4c\",\n        \"base.green\": \"#afb97a\",\n        \"base.green.light\": \"#ccd88c\",\n        \"base.yellow\": \"#c2a86c\",\n        \"base.yellow.light\": \"#e2c47e\",\n        \"base.blue\": \"#44474a\",\n        \"base.blue.light\": \"#5a5e62\",\n        \"base.magenta\": \"#b4be7c\",\n        \"base.magenta.light\": \"#d0dc8e99\",\n        \"base.cyan\": \"#778385\",\n        \"base.cyan.light\": \"#8a989b\"\n      },\n      \"highlight\": {\n        \"editor.foreground\": \"#DDDDDD\",\n        \"editor.background\": \"#000000\",\n        \"editor.active_line.background\": \"#131313\",\n        \"editor.line_number\": \"#8F8F8F\",\n        \"editor.active_line_number\": \"#DDDDDD\",\n        \"editor.invisible\": \"#9E9E9E66\",\n        \"conflict\": \"#D2602D\",\n        \"created\": \"#3f72e2\",\n        \"hidden\": \"#9E9E9E\",\n        \"hint\": \"#b283f8\",\n        \"modified\": \"#B0A878\",\n        \"predictive\": \"#5D5945\",\n        \"syntax\": {\n          \"attribute\": {\n            \"color\": \"#be9a52\"\n          },\n          \"boolean\": {\n            \"color\": \"#E1D797\"\n          },\n          \"comment\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"comment.doc\": {\n            \"color\": \"#9E9E9E\"\n          },\n          \"constant\": {\n            \"color\": \"#E1D797\"\n          },\n          \"constructor\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"embedded\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"function\": {\n            \"color\": \"#E1D797\"\n          },\n          \"keyword\": {\n            \"color\": \"#E19773\"\n          },\n          \"link_text\": {\n            \"color\": \"#A86D3B\",\n            \"font_style\": \"normal\"\n          },\n          \"link_uri\": {\n            \"color\": \"#6F6D66\",\n            \"font_style\": \"italic\"\n          },\n          \"number\": {\n            \"color\": \"#E19773\"\n          },\n          \"string\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.escape\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.regex\": {\n            \"color\": \"#76BA53\"\n          },\n          \"string.special\": {\n            \"color\": \"#E1D797\"\n          },\n          \"string.special.symbol\": {\n            \"color\": \"#E1D797\"\n          },\n          \"tag\": {\n            \"color\": \"#b5af9a\"\n          },\n          \"text.literal\": {\n            \"color\": \"#E1D797\"\n          },\n          \"title\": {\n            \"color\": \"#A76D3B\",\n            \"font_weight\": 600\n          },\n          \"type\": {\n            \"color\": \"#A86D3B\"\n          },\n          \"property\": {\n            \"color\": \"#CACCCA\"\n          },\n          \"variable.special\": {\n            \"color\": \"#E19773\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  }
]