a very good jj gui
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: add GPUI framework skills for Claude

+2439 -1
+1 -1
.claude/settings.json
··· 8 8 } 9 9 }, 10 10 "enabledPlugins": { 11 - "fp-agent@fiberplane-claude-code-plugins": true 11 + "fp@fiberplane-claude-code-plugins": true 12 12 } 13 13 }
+133
.claude/skills/gpui/SKILL.md
··· 1 + --- 2 + name: gpui 3 + description: GPUI framework for building GPU-accelerated UIs in Rust. Use when working with GPUI, building Zed-like applications, or implementing complex UI components. 4 + --- 5 + 6 + # GPUI Framework 7 + 8 + GPUI is a hybrid immediate/retained-mode, GPU-accelerated UI framework from the Zed editor. 9 + 10 + ## Skill Components 11 + 12 + This skill is organized into focused modules: 13 + 14 + | Skill | Use When | 15 + |-------|----------| 16 + | `gpui-core` | App lifecycle, entities, contexts, subscriptions | 17 + | `gpui-elements` | Building UI with div, text, lists, custom elements | 18 + | `gpui-styling` | Flexbox layout, colors, theming, spacing | 19 + | `gpui-keyboard-actions` | Keyboard handling, actions, keybindings | 20 + | `gpui-async-windows` | Async tasks, multiple windows, modals | 21 + | `gpui-text-editing` | Text rendering, editing, diffs, syntax highlighting | 22 + | `gpui-advanced` | Context menus, drag/drop, animations, performance | 23 + 24 + ## Quick Reference 25 + 26 + ### Minimal App 27 + 28 + ```rust 29 + use gpui::*; 30 + 31 + fn main() { 32 + Application::new().run(|cx: &mut App| { 33 + cx.open_window(WindowOptions::default(), |window, cx| { 34 + cx.new(|_| MyView { count: 0 }) 35 + }).unwrap(); 36 + }); 37 + } 38 + 39 + struct MyView { count: usize } 40 + 41 + impl Render for MyView { 42 + fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 43 + div() 44 + .flex() 45 + .size_full() 46 + .justify_center() 47 + .items_center() 48 + .bg(cx.theme().colors().background) 49 + .child(format!("Count: {}", self.count)) 50 + } 51 + } 52 + ``` 53 + 54 + ### Common Patterns 55 + 56 + ```rust 57 + // Flexbox container 58 + div().flex().flex_col().gap_2().p_4() 59 + 60 + // Horizontal layout with centered items 61 + div().flex().flex_row().items_center().gap_2() 62 + 63 + // Full-size element 64 + div().size_full() 65 + 66 + // Theme colors 67 + .bg(cx.theme().colors().surface_background) 68 + .text_color(cx.theme().colors().text) 69 + .border_color(cx.theme().colors().border) 70 + 71 + // Interactive element 72 + div() 73 + .id("my-button") 74 + .cursor_pointer() 75 + .on_click(cx.listener(|this, _, window, cx| { 76 + this.handle_click(window, cx); 77 + })) 78 + 79 + // Focus management 80 + div() 81 + .track_focus(&self.focus_handle) 82 + .on_action(cx.listener(Self::handle_action)) 83 + 84 + // Async task 85 + cx.spawn_in(window, async move |this, cx| { 86 + let data = fetch_data().await; 87 + this.update(&mut cx, |view, _, cx| { 88 + view.data = data; 89 + cx.notify(); 90 + }).ok(); 91 + }).detach(); 92 + ``` 93 + 94 + ### Element Lifecycle 95 + 96 + 1. `request_layout()` - Request size from Taffy layout engine 97 + 2. `prepaint()` - Calculate bounds, insert hitboxes 98 + 3. `paint()` - Draw to canvas 99 + 100 + ### Key Traits 101 + 102 + | Trait | Purpose | 103 + |-------|---------| 104 + | `Render` | Stateful views with `&mut self` | 105 + | `RenderOnce` | Stateless components with owned `self` | 106 + | `Element` | Custom rendering with full control | 107 + | `IntoElement` | Convert to element (strings, options, etc.) | 108 + | `Styled` | Styling methods (fluent builder) | 109 + | `EventEmitter<E>` | Emit typed events | 110 + | `Global` | App-wide state | 111 + 112 + ### Context Types 113 + 114 + | Context | Scope | 115 + |---------|-------| 116 + | `App` | Global app access | 117 + | `Context<T>` | Entity mutations | 118 + | `Window` | Window operations | 119 + | `AsyncApp` | Async-safe app | 120 + | `AsyncWindowContext` | Async-safe window | 121 + 122 + ## Dependencies 123 + 124 + ```toml 125 + [dependencies] 126 + gpui = { git = "https://github.com/zed-industries/zed" } 127 + ``` 128 + 129 + ## Resources 130 + 131 + - Zed source: https://github.com/zed-industries/zed 132 + - GPUI crate: `zed/crates/gpui/` 133 + - UI components: `zed/crates/ui/`
+428
.claude/skills/gpui/advanced.md
··· 1 + --- 2 + name: gpui-advanced 3 + description: GPUI advanced patterns - context menus, tooltips, drag/drop, animations, and performance. Use for complex UI interactions and optimizations. 4 + --- 5 + 6 + # GPUI Advanced Patterns 7 + 8 + ## Context Menus 9 + 10 + ### Building a Context Menu 11 + 12 + ```rust 13 + use gpui::*; 14 + 15 + fn build_context_menu(cx: &mut App) -> Entity<ContextMenu> { 16 + cx.new(|cx| { 17 + ContextMenu::build(cx, |menu, _cx| { 18 + menu.entry("Cut", None, |_cx| { /* handler */ }) 19 + .entry("Copy", None, |_cx| { /* handler */ }) 20 + .entry("Paste", None, |_cx| { /* handler */ }) 21 + .separator() 22 + .entry("Delete", None, |_cx| { /* handler */ }) 23 + }) 24 + }) 25 + } 26 + ``` 27 + 28 + ### Showing Context Menu on Right-Click 29 + 30 + ```rust 31 + div() 32 + .on_mouse_down(MouseButton::Right, cx.listener(|this, event, window, cx| { 33 + let menu = build_context_menu(cx); 34 + window.show_context_menu(event.position, menu, cx); 35 + })) 36 + ``` 37 + 38 + ### Custom Context Menu Items 39 + 40 + ```rust 41 + ContextMenu::build(cx, |menu, cx| { 42 + menu.custom(move |window, cx| { 43 + // Return any element 44 + div() 45 + .p_2() 46 + .child(Label::new("Custom Item")) 47 + .into_any_element() 48 + }) 49 + .entry("Normal Entry", None, |_| {}) 50 + }) 51 + ``` 52 + 53 + ## Tooltips 54 + 55 + ### Simple Tooltip 56 + 57 + ```rust 58 + div() 59 + .id("my-button") 60 + .tooltip(|window, cx| { 61 + Tooltip::text("Click to save") 62 + }) 63 + .child("Save") 64 + ``` 65 + 66 + ### Tooltip with Keybinding 67 + 68 + ```rust 69 + div() 70 + .id("save-button") 71 + .tooltip(|window, cx| { 72 + Tooltip::for_action("Save file", &Save, window, cx) 73 + }) 74 + .child("Save") 75 + ``` 76 + 77 + ### Rich Tooltip 78 + 79 + ```rust 80 + div() 81 + .id("complex-item") 82 + .tooltip(|window, cx| { 83 + Tooltip::rich( 84 + div() 85 + .p_2() 86 + .max_w(px(300.0)) 87 + .child(Label::new("Title").size(LabelSize::Large)) 88 + .child(Label::new("Detailed description here...")) 89 + ) 90 + }) 91 + ``` 92 + 93 + ## Drag and Drop 94 + 95 + ### Draggable Element 96 + 97 + ```rust 98 + div() 99 + .id("draggable-item") 100 + .on_drag(DraggedItem { id: item_id }, |item, window, cx| { 101 + // Return element shown while dragging 102 + div() 103 + .bg(cx.theme().colors().element_background) 104 + .rounded_md() 105 + .p_2() 106 + .child(format!("Dragging: {}", item.id)) 107 + }) 108 + .child("Drag me") 109 + ``` 110 + 111 + ### Drop Target 112 + 113 + ```rust 114 + div() 115 + .on_drop(cx.listener(|this, item: &DraggedItem, window, cx| { 116 + this.handle_drop(item.id, cx); 117 + })) 118 + .drag_over::<DraggedItem>(|style, _, _, _| { 119 + style.bg(hsla(0.0, 0.0, 0.5, 0.2)) 120 + }) 121 + .child("Drop here") 122 + ``` 123 + 124 + ### Drag State 125 + 126 + ```rust 127 + struct MyView { 128 + dragging: Option<DraggedItem>, 129 + } 130 + 131 + impl Render for MyView { 132 + fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 133 + div() 134 + .when(self.dragging.is_some(), |this| { 135 + this.opacity(0.5) 136 + }) 137 + .on_drag_start(cx.listener(|this, item: &DraggedItem, window, cx| { 138 + this.dragging = Some(item.clone()); 139 + })) 140 + .on_drag_end(cx.listener(|this, _, window, cx| { 141 + this.dragging = None; 142 + })) 143 + } 144 + } 145 + ``` 146 + 147 + ## Popover Menus 148 + 149 + ```rust 150 + struct MyView { 151 + popover_handle: PopoverMenuHandle<ContextMenu>, 152 + } 153 + 154 + impl Render for MyView { 155 + fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 156 + PopoverMenu::new("my-popover") 157 + .trigger( 158 + Button::new("trigger", "Open Menu") 159 + .style(ButtonStyle::Ghost) 160 + ) 161 + .menu(move |window, cx| { 162 + Some(ContextMenu::build(cx, |menu, _| { 163 + menu.entry("Option 1", None, |_| {}) 164 + .entry("Option 2", None, |_| {}) 165 + })) 166 + }) 167 + .attach(Corner::BottomLeft) 168 + .offset(point(px(0.0), px(4.0))) 169 + } 170 + } 171 + 172 + // Programmatic control 173 + self.popover_handle.toggle(window, cx); 174 + self.popover_handle.show(window, cx); 175 + self.popover_handle.hide(cx); 176 + ``` 177 + 178 + ## Animations 179 + 180 + ### Basic Animation 181 + 182 + ```rust 183 + div() 184 + .with_animation( 185 + "fade-in", 186 + Animation::new(Duration::from_millis(200)) 187 + .with_easing(Easing::EaseOut), 188 + |style, progress| { 189 + style.opacity(progress) 190 + }, 191 + ) 192 + ``` 193 + 194 + ### Keyframe Animation 195 + 196 + ```rust 197 + div() 198 + .with_animation( 199 + "slide-in", 200 + Animation::new(Duration::from_millis(300)) 201 + .with_easing(Easing::EaseInOut), 202 + |style, progress| { 203 + let offset = px(20.0) * (1.0 - progress); 204 + style 205 + .opacity(progress) 206 + .transform(Transform::translate_y(offset)) 207 + }, 208 + ) 209 + ``` 210 + 211 + ### Conditional Animation 212 + 213 + ```rust 214 + div() 215 + .when(is_visible, |this| { 216 + this.with_animation( 217 + "appear", 218 + Animation::new(Duration::from_millis(150)), 219 + |style, t| style.opacity(t), 220 + ) 221 + }) 222 + ``` 223 + 224 + ## Hover and Click States 225 + 226 + ```rust 227 + div() 228 + .id("interactive-item") 229 + .cursor_pointer() 230 + .bg(cx.theme().colors().element_background) 231 + .hover(|style| { 232 + style.bg(cx.theme().colors().element_hover) 233 + }) 234 + .active(|style| { 235 + style.bg(cx.theme().colors().element_active) 236 + }) 237 + .on_click(cx.listener(|this, _, window, cx| { 238 + this.handle_click(window, cx); 239 + })) 240 + ``` 241 + 242 + ## Performance Patterns 243 + 244 + ### Memoization with Cached Elements 245 + 246 + ```rust 247 + struct MyView { 248 + cached_expensive_element: Option<AnyElement>, 249 + cache_key: usize, 250 + } 251 + 252 + impl Render for MyView { 253 + fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 254 + let current_key = self.compute_cache_key(); 255 + 256 + if self.cache_key != current_key || self.cached_expensive_element.is_none() { 257 + self.cached_expensive_element = Some(self.render_expensive(cx).into_any_element()); 258 + self.cache_key = current_key; 259 + } 260 + 261 + div().child(self.cached_expensive_element.clone().unwrap()) 262 + } 263 + } 264 + ``` 265 + 266 + ### Virtualization for Large Lists 267 + 268 + ```rust 269 + // Use uniform_list for large datasets 270 + uniform_list( 271 + self.scroll_handle.clone(), 272 + "large-list", 273 + self.items.len(), 274 + |this, visible_range, window, cx| { 275 + // Only render visible items 276 + visible_range 277 + .map(|ix| this.render_item(ix, window, cx)) 278 + .collect() 279 + }, 280 + ) 281 + ``` 282 + 283 + ### Pre-fetching Around Viewport 284 + 285 + ```rust 286 + fn prefetch_around_selection(&self, selected: usize, cx: &mut Context<Self>) { 287 + let prefetch_before = 2; 288 + let prefetch_after = 2; 289 + 290 + let start = selected.saturating_sub(prefetch_before); 291 + let end = (selected + prefetch_after + 1).min(self.items.len()); 292 + 293 + for ix in start..end { 294 + self.ensure_item_loaded(ix, cx); 295 + } 296 + } 297 + ``` 298 + 299 + ### SmallVec for Small Collections 300 + 301 + ```rust 302 + use smallvec::SmallVec; 303 + 304 + // Stack-allocate up to 8 items 305 + let mut highlights: SmallVec<[(Range<usize>, Hsla); 8]> = SmallVec::new(); 306 + highlights.push((0..10, hsla(0.0, 1.0, 0.5, 1.0))); 307 + ``` 308 + 309 + ## Hitbox Optimization 310 + 311 + ```rust 312 + impl Element for MyElement { 313 + fn prepaint(&mut self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App) { 314 + // Only insert hitbox if interactive 315 + if self.is_interactive { 316 + self.hitbox = Some(window.insert_hitbox(bounds, false)); 317 + } 318 + } 319 + 320 + fn paint(&mut self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App) { 321 + // Check if mouse is over this element 322 + if let Some(hitbox) = &self.hitbox { 323 + if hitbox.is_hovered(window) { 324 + // Paint hover state 325 + } 326 + } 327 + } 328 + } 329 + ``` 330 + 331 + ## Scroll Performance 332 + 333 + ```rust 334 + struct ScrollableView { 335 + scroll_handle: ScrollHandle, 336 + last_scroll_position: Point<Pixels>, 337 + } 338 + 339 + impl Render for ScrollableView { 340 + fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 341 + div() 342 + .size_full() 343 + .overflow_y_scroll() 344 + .track_scroll(&self.scroll_handle) 345 + .on_scroll_wheel(cx.listener(|this, event: &ScrollWheelEvent, window, cx| { 346 + // Custom scroll handling if needed 347 + })) 348 + .children(/* ... */) 349 + } 350 + } 351 + ``` 352 + 353 + ## Deferred Rendering (Overlays) 354 + 355 + ```rust 356 + // Render overlay after other content 357 + fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 358 + div() 359 + .relative() 360 + .child(self.render_main_content(cx)) 361 + .child( 362 + deferred( 363 + anchored() 364 + .position(self.popup_position) 365 + .child(self.render_popup(cx)) 366 + ) 367 + ) 368 + } 369 + ``` 370 + 371 + ## Click Outside to Dismiss 372 + 373 + ```rust 374 + struct Popup { 375 + is_open: bool, 376 + } 377 + 378 + impl Render for Popup { 379 + fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 380 + if !self.is_open { 381 + return div().into_any_element(); 382 + } 383 + 384 + // Capture all clicks 385 + div() 386 + .absolute() 387 + .inset(px(0.0)) 388 + .on_mouse_down(MouseButton::Left, cx.listener(|this, event, window, cx| { 389 + // Check if click is outside popup bounds 390 + if !this.popup_bounds.contains(&event.position) { 391 + this.is_open = false; 392 + cx.notify(); 393 + } 394 + })) 395 + .child( 396 + div() 397 + .absolute() 398 + .top(self.position.y) 399 + .left(self.position.x) 400 + .child(self.render_popup_content(cx)) 401 + ) 402 + .into_any_element() 403 + } 404 + } 405 + ``` 406 + 407 + ## Keyboard Trap (Modal Focus) 408 + 409 + ```rust 410 + impl Render for Modal { 411 + fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 412 + div() 413 + .track_focus(&self.focus_handle) 414 + // Trap tab key within modal 415 + .on_action(cx.listener(|this, _: &Tab, window, cx| { 416 + this.focus_next(window, cx); 417 + })) 418 + .on_action(cx.listener(|this, _: &ShiftTab, window, cx| { 419 + this.focus_previous(window, cx); 420 + })) 421 + // Dismiss on escape 422 + .on_action(cx.listener(|this, _: &Escape, window, cx| { 423 + cx.emit(DismissEvent); 424 + })) 425 + .child(/* modal content */) 426 + } 427 + } 428 + ```
+370
.claude/skills/gpui/async-windows.md
··· 1 + --- 2 + name: gpui-async-windows 3 + description: GPUI async patterns and multiple window management. Use when spawning background tasks, opening new windows, or managing window communication. 4 + --- 5 + 6 + # GPUI Async and Windows 7 + 8 + ## Spawning Async Tasks 9 + 10 + ### From Context (main thread) 11 + 12 + ```rust 13 + impl MyView { 14 + fn load_data(&mut self, window: &mut Window, cx: &mut Context<Self>) { 15 + // spawn_in - keeps window context, weak reference to self 16 + cx.spawn_in(window, async move |this, cx| { 17 + // this: WeakEntity<Self> 18 + // cx: AsyncWindowContext 19 + 20 + let data = fetch_data().await; 21 + 22 + // Update view back on main thread 23 + this.update(&mut cx, |view, window, cx| { 24 + view.data = Some(data); 25 + cx.notify(); 26 + }).ok(); 27 + }).detach(); 28 + } 29 + } 30 + ``` 31 + 32 + ### Background Executor (off main thread) 33 + 34 + ```rust 35 + // CPU-intensive work 36 + let result = cx.background_executor().spawn(async move { 37 + expensive_computation() 38 + }).await; 39 + 40 + // With priority 41 + cx.background_executor() 42 + .spawn_with_priority(Priority::Low, async move { 43 + background_work() 44 + }) 45 + .detach(); 46 + ``` 47 + 48 + ### Foreground Executor (main thread) 49 + 50 + ```rust 51 + cx.foreground_executor().spawn(async move { 52 + // Runs on main thread 53 + }).detach(); 54 + ``` 55 + 56 + ## Task Management 57 + 58 + ```rust 59 + struct MyView { 60 + loading_task: Option<Task<()>>, 61 + } 62 + 63 + impl MyView { 64 + fn start_loading(&mut self, window: &mut Window, cx: &mut Context<Self>) { 65 + // Cancel previous task by dropping 66 + self.loading_task = None; 67 + 68 + self.loading_task = Some(cx.spawn_in(window, async move |this, cx| { 69 + let result = load_something().await; 70 + 71 + this.update(&mut cx, |view, window, cx| { 72 + view.data = result; 73 + view.loading_task = None; 74 + cx.notify(); 75 + }).ok(); 76 + })); 77 + } 78 + 79 + fn cancel_loading(&mut self) { 80 + // Dropping the task cancels it 81 + self.loading_task = None; 82 + } 83 + } 84 + ``` 85 + 86 + ## AsyncApp and AsyncWindowContext 87 + 88 + For holding context across await points: 89 + 90 + ```rust 91 + // AsyncApp - app-level async context 92 + cx.spawn(async move |cx: AsyncApp| { 93 + // Read global state 94 + let value = cx.read_global::<MyGlobal, _>(|global, _| { 95 + global.some_value.clone() 96 + })?; 97 + 98 + // Update global state 99 + cx.update_global::<MyGlobal, _>(|global, _| { 100 + global.count += 1; 101 + })?; 102 + 103 + Ok(()) 104 + }); 105 + 106 + // AsyncWindowContext - window-scoped async context 107 + cx.spawn_in(window, async move |this, cx: AsyncWindowContext| { 108 + // Update entity 109 + this.update(&mut cx, |view, window, cx| { 110 + view.do_something(window, cx); 111 + })?; 112 + 113 + Ok(()) 114 + }); 115 + ``` 116 + 117 + ## Creating Windows 118 + 119 + ### Basic Window 120 + 121 + ```rust 122 + cx.open_window( 123 + WindowOptions { 124 + titlebar: Some(TitlebarOptions { 125 + title: Some("My Window".into()), 126 + ..Default::default() 127 + }), 128 + window_bounds: Some(WindowBounds::Windowed(Bounds { 129 + origin: point(px(100.0), px(100.0)), 130 + size: size(px(800.0), px(600.0)), 131 + })), 132 + ..Default::default() 133 + }, 134 + |window, cx| { 135 + cx.new(|cx| MyView::new(cx)) 136 + }, 137 + )?; 138 + ``` 139 + 140 + ### Window Options 141 + 142 + ```rust 143 + WindowOptions { 144 + // Title bar 145 + titlebar: Some(TitlebarOptions { 146 + title: Some("Title".into()), 147 + appears_transparent: false, 148 + traffic_light_position: None, // macOS traffic lights 149 + }), 150 + 151 + // Bounds 152 + window_bounds: Some(WindowBounds::Windowed(bounds)), 153 + // or WindowBounds::Maximized, WindowBounds::Fullscreen 154 + 155 + // Behavior 156 + focus: true, 157 + show: true, 158 + kind: WindowKind::Normal, // or PopUp, Menu 159 + 160 + // Display 161 + display_id: None, // Specific monitor 162 + 163 + // macOS specific 164 + app_id: None, 165 + window_background: WindowBackgroundAppearance::Opaque, 166 + 167 + ..Default::default() 168 + } 169 + ``` 170 + 171 + ### From Async Context 172 + 173 + ```rust 174 + cx.spawn(async move |cx: AsyncApp| { 175 + cx.open_window(options, |window, cx| { 176 + cx.new(|cx| MyView::new(cx)) 177 + })?; 178 + Ok(()) 179 + }); 180 + ``` 181 + 182 + ## Window Handles 183 + 184 + ### WindowHandle<V> (typed) 185 + 186 + ```rust 187 + let handle: WindowHandle<MyView> = cx.open_window(options, build)?; 188 + 189 + // Read root view 190 + let value = handle.read(cx)?.some_field; 191 + 192 + // Update root view 193 + handle.update(cx, |view, window, cx| { 194 + view.do_something(window, cx); 195 + })?; 196 + 197 + // Read with callback 198 + handle.read_with(cx, |view, cx| { 199 + view.get_value() 200 + })?; 201 + ``` 202 + 203 + ### AnyWindowHandle (untyped) 204 + 205 + ```rust 206 + let any_handle: AnyWindowHandle = handle.into(); 207 + 208 + // Downcast back to typed 209 + if let Some(typed) = any_handle.downcast::<MyView>() { 210 + typed.update(cx, |view, window, cx| { /* ... */ })?; 211 + } 212 + 213 + // Update without type 214 + any_handle.update(cx, |any_view, window, cx| { 215 + // any_view: AnyView 216 + })?; 217 + ``` 218 + 219 + ## Window-to-Window Communication 220 + 221 + ### Via Global State 222 + 223 + ```rust 224 + // Global event bus 225 + struct AppEvents { 226 + listeners: Vec<Box<dyn Fn(&AppEvent, &mut App)>>, 227 + } 228 + impl Global for AppEvents {} 229 + 230 + // Window 1: emit event 231 + cx.update_global::<AppEvents, _>(|events, cx| { 232 + for listener in &events.listeners { 233 + listener(&AppEvent::DataChanged, cx); 234 + } 235 + }); 236 + 237 + // Window 2: subscribe to events 238 + let subscription = cx.observe_global::<AppEvents>(|view, cx| { 239 + view.refresh(cx); 240 + }); 241 + ``` 242 + 243 + ### Via Entity 244 + 245 + ```rust 246 + // Shared model between windows 247 + let shared_model: Entity<SharedData> = cx.new(|_| SharedData::new()); 248 + 249 + // Window 1: updates model 250 + shared_model.update(cx, |model, cx| { 251 + model.value = 42; 252 + cx.notify(); 253 + }); 254 + 255 + // Window 2: observes model 256 + cx.observe(&shared_model, |view, model, cx| { 257 + view.sync_from_model(model.read(cx), cx); 258 + }); 259 + ``` 260 + 261 + ### Via Window Handle 262 + 263 + ```rust 264 + // Store handle to other window 265 + struct Window1 { 266 + window2_handle: Option<WindowHandle<Window2>>, 267 + } 268 + 269 + // Communicate 270 + if let Some(handle) = &self.window2_handle { 271 + handle.update(cx, |window2, window, cx| { 272 + window2.receive_message(message, window, cx); 273 + }).ok(); 274 + } 275 + ``` 276 + 277 + ## Modal Windows 278 + 279 + Pattern for modal dialogs: 280 + 281 + ```rust 282 + struct ModalLayer { 283 + active_modal: Option<ActiveModal>, 284 + } 285 + 286 + struct ActiveModal { 287 + view: AnyView, 288 + previous_focus: Option<FocusHandle>, 289 + subscriptions: Vec<Subscription>, 290 + } 291 + 292 + impl ModalLayer { 293 + fn show_modal<V: ModalView>( 294 + &mut self, 295 + build: impl FnOnce(&mut Window, &mut App) -> Entity<V>, 296 + window: &mut Window, 297 + cx: &mut Context<Self>, 298 + ) { 299 + let previous_focus = window.focused(cx); 300 + let modal = cx.new(|cx| build(window, cx)); 301 + 302 + // Subscribe to dismiss 303 + let subscription = cx.subscribe(&modal, |this, _, _: &DismissEvent, window, cx| { 304 + this.hide_modal(window, cx); 305 + }); 306 + 307 + self.active_modal = Some(ActiveModal { 308 + view: modal.into(), 309 + previous_focus, 310 + subscriptions: vec![subscription], 311 + }); 312 + 313 + // Focus modal 314 + cx.defer_in(window, |this, window, cx| { 315 + if let Some(modal) = &this.active_modal { 316 + window.focus(&modal.focus_handle); 317 + } 318 + }); 319 + } 320 + 321 + fn hide_modal(&mut self, window: &mut Window, cx: &mut Context<Self>) { 322 + if let Some(modal) = self.active_modal.take() { 323 + // Restore previous focus 324 + if let Some(handle) = modal.previous_focus { 325 + window.focus(&handle); 326 + } 327 + } 328 + cx.notify(); 329 + } 330 + } 331 + ``` 332 + 333 + ## Timeouts and Intervals 334 + 335 + ```rust 336 + // One-shot timer 337 + cx.spawn_in(window, async move |this, cx| { 338 + cx.background_executor().timer(Duration::from_secs(1)).await; 339 + this.update(&mut cx, |view, _, cx| { 340 + view.on_timeout(cx); 341 + }).ok(); 342 + }).detach(); 343 + 344 + // Repeating interval 345 + cx.spawn_in(window, async move |this, cx| { 346 + loop { 347 + cx.background_executor().timer(Duration::from_millis(100)).await; 348 + if this.update(&mut cx, |view, _, cx| { 349 + view.tick(cx) // returns false to stop 350 + }).unwrap_or(false) == false { 351 + break; 352 + } 353 + } 354 + }).detach(); 355 + ``` 356 + 357 + ## Task Priority 358 + 359 + ```rust 360 + pub enum Priority { 361 + Realtime, // Immediate, blocks UI 362 + High, // Important background work 363 + Medium, // Default 364 + Low, // Can wait 365 + } 366 + 367 + cx.spawn_in_with_priority(Priority::Low, window, async move |this, cx| { 368 + // Low priority background work 369 + }); 370 + ```
+212
.claude/skills/gpui/core.md
··· 1 + --- 2 + name: gpui-core 3 + description: GPUI framework fundamentals - App, Window, Context, Entity system, and lifecycle. Use when creating GPUI applications, managing state, or understanding the framework architecture. 4 + --- 5 + 6 + # GPUI Core Concepts 7 + 8 + GPUI is a hybrid immediate/retained-mode, GPU-accelerated UI framework from the Zed editor. 9 + 10 + ## Application Lifecycle 11 + 12 + ```rust 13 + use gpui::*; 14 + 15 + fn main() { 16 + Application::new().run(|cx: &mut App| { 17 + cx.open_window( 18 + WindowOptions::default(), 19 + |window, cx| { 20 + cx.new(|cx| MyView::new(cx)) 21 + }, 22 + ).unwrap(); 23 + }); 24 + } 25 + ``` 26 + 27 + ## The Render Trait 28 + 29 + Views must implement `Render`: 30 + 31 + ```rust 32 + struct MyView { 33 + count: usize, 34 + } 35 + 36 + impl Render for MyView { 37 + fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 38 + div() 39 + .flex() 40 + .size_full() 41 + .child(format!("Count: {}", self.count)) 42 + } 43 + } 44 + ``` 45 + 46 + ## Entity System 47 + 48 + Entities are the core state containers. Use `Entity<T>` for strong references, `WeakEntity<T>` for weak: 49 + 50 + ```rust 51 + // Create an entity 52 + let entity: Entity<MyModel> = cx.new(|cx| MyModel::new()); 53 + 54 + // Read entity state 55 + let value = entity.read(cx).some_field; 56 + 57 + // Update entity state 58 + entity.update(cx, |model, cx| { 59 + model.some_field = new_value; 60 + cx.notify(); // Trigger re-render 61 + }); 62 + 63 + // Weak reference (won't keep entity alive) 64 + let weak: WeakEntity<MyModel> = entity.downgrade(); 65 + if let Some(entity) = weak.upgrade() { 66 + // Entity still exists 67 + } 68 + ``` 69 + 70 + ## Context Types 71 + 72 + Different contexts for different scopes: 73 + 74 + | Context | Purpose | 75 + |---------|---------| 76 + | `App` / `&mut App` | Global app state access | 77 + | `Context<T>` | Entity-specific context for mutations | 78 + | `Window` / `&mut Window` | Window-specific operations | 79 + | `AsyncApp` | Async-safe app context | 80 + | `AsyncWindowContext` | Async-safe window context | 81 + 82 + ```rust 83 + // In a view method 84 + fn some_method(&mut self, window: &mut Window, cx: &mut Context<Self>) { 85 + // cx.notify() triggers re-render 86 + // cx.emit(event) emits events 87 + // cx.spawn() spawns async tasks 88 + // window.focus() manages focus 89 + } 90 + ``` 91 + 92 + ## Subscriptions and Observations 93 + 94 + Subscribe to entity events: 95 + 96 + ```rust 97 + // Subscribe to events from another entity 98 + let subscription = cx.subscribe(&other_entity, |this, emitter, event, cx| { 99 + // Handle event 100 + }); 101 + 102 + // Observe any changes (when notify() is called) 103 + let subscription = cx.observe(&other_entity, |this, observed, cx| { 104 + // Entity was updated 105 + }); 106 + 107 + // Observe global state changes 108 + let subscription = cx.observe_global::<MyGlobal>(|this, cx| { 109 + // Global changed 110 + }); 111 + 112 + // Subscriptions auto-cleanup on drop - store them to keep alive 113 + struct MyView { 114 + _subscriptions: Vec<Subscription>, 115 + } 116 + ``` 117 + 118 + ## Global State 119 + 120 + For app-wide state: 121 + 122 + ```rust 123 + // Define a global 124 + struct AppState { 125 + user: Option<User>, 126 + } 127 + impl Global for AppState {} 128 + 129 + // Set global (once at startup) 130 + cx.set_global(AppState { user: None }); 131 + 132 + // Read global 133 + let state = cx.global::<AppState>(); 134 + 135 + // Update global 136 + cx.update_global::<AppState, _>(|state, cx| { 137 + state.user = Some(user); 138 + }); 139 + ``` 140 + 141 + ## Event Emitting 142 + 143 + Entities can emit typed events: 144 + 145 + ```rust 146 + struct MyView; 147 + 148 + // Define event types 149 + struct SelectionChanged(usize); 150 + 151 + // Implement EventEmitter 152 + impl EventEmitter<SelectionChanged> for MyView {} 153 + 154 + // Emit events 155 + fn select(&mut self, index: usize, cx: &mut Context<Self>) { 156 + cx.emit(SelectionChanged(index)); 157 + } 158 + 159 + // Subscribe from another entity 160 + cx.subscribe(&my_view, |this, _emitter, event: &SelectionChanged, cx| { 161 + println!("Selected: {}", event.0); 162 + }); 163 + ``` 164 + 165 + ## Focus Management 166 + 167 + ```rust 168 + impl MyView { 169 + fn new(cx: &mut Context<Self>) -> Self { 170 + let focus_handle = cx.focus_handle(); 171 + 172 + // React to focus changes 173 + cx.on_focus(&focus_handle, window, Self::handle_focus); 174 + cx.on_blur(&focus_handle, window, Self::handle_blur); 175 + 176 + Self { focus_handle } 177 + } 178 + } 179 + 180 + // In render, make element focusable 181 + div() 182 + .track_focus(&self.focus_handle) 183 + .child("Focusable content") 184 + 185 + // Programmatically focus 186 + window.focus(&self.focus_handle); 187 + ``` 188 + 189 + ## Deferred Execution 190 + 191 + ```rust 192 + // Run after current update cycle 193 + cx.defer(|cx| { 194 + // Deferred work 195 + }); 196 + 197 + // Run in window context after update 198 + cx.defer_in(window, |this, window, cx| { 199 + window.focus(&this.focus_handle); 200 + }); 201 + ``` 202 + 203 + ## Notify for Re-renders 204 + 205 + Call `cx.notify()` to trigger re-render when state changes: 206 + 207 + ```rust 208 + fn increment(&mut self, cx: &mut Context<Self>) { 209 + self.count += 1; 210 + cx.notify(); // Required to update UI 211 + } 212 + ```
+276
.claude/skills/gpui/elements.md
··· 1 + --- 2 + name: gpui-elements 3 + description: GPUI element system - div, text, lists, images, and custom elements. Use when building UI components, creating layouts, or implementing custom rendering. 4 + --- 5 + 6 + # GPUI Elements 7 + 8 + Elements are the building blocks of GPUI UIs. They follow a three-phase rendering pipeline. 9 + 10 + ## Basic Elements 11 + 12 + ### Div (Container) 13 + 14 + The primary container element: 15 + 16 + ```rust 17 + div() 18 + .id("my-container") 19 + .flex() 20 + .flex_col() 21 + .gap_2() 22 + .p_4() 23 + .bg(cx.theme().colors().surface_background) 24 + .child(Label::new("Hello")) 25 + .child(Button::new("btn", "Click me")) 26 + ``` 27 + 28 + ### Text 29 + 30 + For text content: 31 + 32 + ```rust 33 + // Simple text (converts to element) 34 + "Hello, World!" 35 + 36 + // Styled text 37 + div() 38 + .text_color(hsla(0.0, 0.0, 1.0, 1.0)) 39 + .text_size(px(16.0)) 40 + .child("Styled text") 41 + 42 + // Shaped text with runs (for syntax highlighting) 43 + let line = window.text_system().shape_line( 44 + text.into(), 45 + font_size, 46 + &[TextRun { len: text.len(), font, color }], 47 + None, 48 + ); 49 + ``` 50 + 51 + ### Images 52 + 53 + ```rust 54 + img(image_source) 55 + .size(px(100.0)) 56 + .rounded_md() 57 + 58 + // From file path 59 + img(PathBuf::from("/path/to/image.png")) 60 + 61 + // SVG icon 62 + svg() 63 + .path("icons/check.svg") 64 + .size(px(16.0)) 65 + .text_color(Color::Success.color(cx)) 66 + ``` 67 + 68 + ## Lists 69 + 70 + ### Uniform List (same height items) 71 + 72 + For large lists with uniform item heights - renders only visible items: 73 + 74 + ```rust 75 + uniform_list( 76 + self.scroll_handle.clone(), 77 + "item-list", 78 + items.len(), 79 + |this, visible_range, window, cx| { 80 + visible_range 81 + .map(|ix| this.render_item(ix, window, cx)) 82 + .collect() 83 + }, 84 + ) 85 + .track_scroll(self.scroll_handle.clone()) 86 + ``` 87 + 88 + ### List (variable height items) 89 + 90 + For lists with variable item heights: 91 + 92 + ```rust 93 + list(self.list_state.clone()) 94 + .size_full() 95 + .with_sizing_behavior(ListSizingBehavior::Auto) 96 + ``` 97 + 98 + ### Scroll Handle 99 + 100 + ```rust 101 + struct MyView { 102 + scroll_handle: UniformListScrollHandle, 103 + } 104 + 105 + impl MyView { 106 + fn new() -> Self { 107 + Self { 108 + scroll_handle: UniformListScrollHandle::new(), 109 + } 110 + } 111 + 112 + fn scroll_to_item(&mut self, index: usize) { 113 + self.scroll_handle.scroll_to_item(index, ScrollStrategy::Center); 114 + } 115 + } 116 + ``` 117 + 118 + ## The Element Trait 119 + 120 + Custom elements implement the three-phase pipeline: 121 + 122 + ```rust 123 + impl Element for MyElement { 124 + type RequestLayoutState = MyLayoutState; 125 + type PrepaintState = MyPrepaintState; 126 + 127 + // Phase 1: Request layout from Taffy 128 + fn request_layout( 129 + &mut self, 130 + id: Option<&GlobalElementId>, 131 + window: &mut Window, 132 + cx: &mut App, 133 + ) -> (LayoutId, Self::RequestLayoutState) { 134 + let mut style = Style::default(); 135 + style.size.width = relative(1.).into(); 136 + style.size.height = px(100.).into(); 137 + 138 + let layout_id = window.request_layout(style, None, cx); 139 + (layout_id, MyLayoutState { /* ... */ }) 140 + } 141 + 142 + // Phase 2: Prepaint - calculate bounds, insert hitboxes 143 + fn prepaint( 144 + &mut self, 145 + id: Option<&GlobalElementId>, 146 + bounds: Bounds<Pixels>, 147 + state: &mut Self::RequestLayoutState, 148 + window: &mut Window, 149 + cx: &mut App, 150 + ) -> Self::PrepaintState { 151 + // Insert hitbox for click detection 152 + let hitbox = window.insert_hitbox(bounds, false); 153 + 154 + MyPrepaintState { hitbox, bounds } 155 + } 156 + 157 + // Phase 3: Paint to canvas 158 + fn paint( 159 + &mut self, 160 + id: Option<&GlobalElementId>, 161 + bounds: Bounds<Pixels>, 162 + state: &mut Self::RequestLayoutState, 163 + prepaint: &mut Self::PrepaintState, 164 + window: &mut Window, 165 + cx: &mut App, 166 + ) { 167 + // Paint background 168 + window.paint_quad(fill(bounds, cx.theme().colors().background)); 169 + 170 + // Paint text, shapes, etc. 171 + } 172 + } 173 + ``` 174 + 175 + ## RenderOnce vs Render 176 + 177 + - `Render` - For stateful views (takes `&mut self`) 178 + - `RenderOnce` - For stateless components (takes owned `self`) 179 + 180 + ```rust 181 + // Stateful view 182 + impl Render for MyView { 183 + fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 184 + div().child("stateful") 185 + } 186 + } 187 + 188 + // Stateless component 189 + struct MyComponent { 190 + label: SharedString, 191 + } 192 + 193 + impl RenderOnce for MyComponent { 194 + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { 195 + div().child(self.label) 196 + } 197 + } 198 + 199 + // Use stateless component 200 + div().child(MyComponent { label: "hello".into() }) 201 + ``` 202 + 203 + ## IntoElement Trait 204 + 205 + Types that can become elements: 206 + 207 + ```rust 208 + // Strings 209 + "hello" // impl IntoElement 210 + 211 + // Options (renders nothing for None) 212 + let maybe_label: Option<Label> = Some(Label::new("hi")); 213 + div().child(maybe_label) 214 + 215 + // Vectors 216 + let items: Vec<impl IntoElement> = vec![...]; 217 + div().children(items) 218 + ``` 219 + 220 + ## Deferred Elements 221 + 222 + Render later in the paint order (for overlays): 223 + 224 + ```rust 225 + deferred( 226 + div() 227 + .absolute() 228 + .child("Overlay content") 229 + ) 230 + ``` 231 + 232 + ## Anchored Elements (Popovers) 233 + 234 + Position relative to an anchor: 235 + 236 + ```rust 237 + anchored() 238 + .position(AnchoredPosition::Below(anchor_bounds)) 239 + .child( 240 + div() 241 + .elevation_3(cx) 242 + .child("Popover content") 243 + ) 244 + ``` 245 + 246 + ## Canvas (Custom Drawing) 247 + 248 + For imperative drawing: 249 + 250 + ```rust 251 + canvas( 252 + |bounds, window, cx| { 253 + // Prepaint phase - return state 254 + PrepaintState { bounds } 255 + }, 256 + |bounds, state, window, cx| { 257 + // Paint phase 258 + window.paint_quad(fill(bounds, hsla(0.0, 1.0, 0.5, 1.0))); 259 + }, 260 + ) 261 + ``` 262 + 263 + ## Parent Element Methods 264 + 265 + ```rust 266 + div() 267 + .child(single_element) // Add one child 268 + .children(vec_of_elements) // Add multiple children 269 + .children_any(any_elements) // Add AnyElement children 270 + .when(condition, |this| { // Conditional children 271 + this.child(Label::new("Shown when true")) 272 + }) 273 + .when_some(option, |this, value| { 274 + this.child(Label::new(value)) 275 + }) 276 + ```
+286
.claude/skills/gpui/keyboard-actions.md
··· 1 + --- 2 + name: gpui-keyboard-actions 3 + description: GPUI keyboard handling, actions, and keybindings. Use when implementing keyboard navigation, shortcuts, or action dispatch. 4 + --- 5 + 6 + # GPUI Keyboard and Actions 7 + 8 + ## Defining Actions 9 + 10 + Actions are the bridge between keybindings and behavior: 11 + 12 + ```rust 13 + use gpui::actions; 14 + 15 + // Simple actions (no data) 16 + actions!(my_app, [ 17 + MoveUp, 18 + MoveDown, 19 + SelectAll, 20 + Delete, 21 + Confirm, 22 + Cancel, 23 + ]); 24 + 25 + // Action with data 26 + #[derive(Clone, PartialEq, Deserialize)] 27 + struct GoToLine { 28 + line: usize, 29 + } 30 + 31 + impl_actions!(my_app, [GoToLine]); 32 + ``` 33 + 34 + ## Registering Action Handlers 35 + 36 + In your view's render or setup: 37 + 38 + ```rust 39 + impl Render for MyView { 40 + fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 41 + // Register actions on the element 42 + div() 43 + .track_focus(&self.focus_handle) 44 + .on_action(cx.listener(Self::move_up)) 45 + .on_action(cx.listener(Self::move_down)) 46 + .on_action(cx.listener(Self::select_all)) 47 + .child(/* ... */) 48 + } 49 + } 50 + 51 + impl MyView { 52 + fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) { 53 + self.selected_index = self.selected_index.saturating_sub(1); 54 + cx.notify(); 55 + } 56 + 57 + fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) { 58 + self.selected_index = (self.selected_index + 1).min(self.items.len() - 1); 59 + cx.notify(); 60 + } 61 + } 62 + ``` 63 + 64 + ## Keybindings 65 + 66 + Bind keys to actions in your keymap: 67 + 68 + ```rust 69 + // In app setup 70 + cx.bind_keys([ 71 + KeyBinding::new("up", MoveUp, None), 72 + KeyBinding::new("down", MoveDown, None), 73 + KeyBinding::new("enter", Confirm, None), 74 + KeyBinding::new("escape", Cancel, None), 75 + KeyBinding::new("cmd-a", SelectAll, None), // macOS 76 + KeyBinding::new("ctrl-a", SelectAll, None), // Linux/Windows 77 + ]); 78 + ``` 79 + 80 + ### Key Syntax 81 + 82 + ```rust 83 + // Modifiers 84 + "cmd-s" // Command (macOS) 85 + "ctrl-s" // Control 86 + "alt-s" // Alt/Option 87 + "shift-s" // Shift 88 + "cmd-shift-s" // Multiple modifiers 89 + 90 + // Special keys 91 + "enter" 92 + "escape" 93 + "tab" 94 + "space" 95 + "backspace" 96 + "delete" 97 + "up" / "down" / "left" / "right" 98 + "home" / "end" 99 + "pageup" / "pagedown" 100 + "f1" through "f12" 101 + ``` 102 + 103 + ## Key Context 104 + 105 + Bind actions only in specific contexts: 106 + 107 + ```rust 108 + // Define context 109 + window.set_key_context(KeyContext::new_with_defaults()); 110 + 111 + // Bind with context 112 + KeyBinding::new("j", MoveDown, Some("Editor")) 113 + KeyBinding::new("j", NextItem, Some("List")) 114 + ``` 115 + 116 + In render, set the context: 117 + 118 + ```rust 119 + div() 120 + .key_context("Editor") // This subtree uses "Editor" context 121 + .on_action(cx.listener(Self::move_down)) 122 + ``` 123 + 124 + ## Direct Key Event Handling 125 + 126 + For custom key handling without actions: 127 + 128 + ```rust 129 + div() 130 + .on_key_down(cx.listener(|this, event: &KeyDownEvent, window, cx| { 131 + match &event.keystroke.key { 132 + "j" if event.keystroke.modifiers.control => { 133 + this.move_down(window, cx); 134 + } 135 + _ => {} 136 + } 137 + })) 138 + ``` 139 + 140 + ## Key Event Types 141 + 142 + ```rust 143 + // Key down 144 + .on_key_down(cx.listener(|this, event: &KeyDownEvent, window, cx| { 145 + let key = &event.keystroke.key; 146 + let mods = &event.keystroke.modifiers; 147 + // mods.control, mods.alt, mods.shift, mods.platform (cmd on mac) 148 + })) 149 + 150 + // Key up 151 + .on_key_up(cx.listener(|this, event: &KeyUpEvent, window, cx| { 152 + // Handle key release 153 + })) 154 + 155 + // Modifiers changed (for UI feedback) 156 + .on_modifiers_changed(cx.listener(|this, event: &ModifiersChangedEvent, window, cx| { 157 + this.alt_held = event.modifiers.alt; 158 + cx.notify(); 159 + })) 160 + ``` 161 + 162 + ## Focus and Action Dispatch 163 + 164 + Actions bubble up from the focused element: 165 + 166 + ```rust 167 + struct MyView { 168 + focus_handle: FocusHandle, 169 + } 170 + 171 + impl MyView { 172 + fn new(cx: &mut Context<Self>) -> Self { 173 + Self { 174 + focus_handle: cx.focus_handle(), 175 + } 176 + } 177 + } 178 + 179 + impl Render for MyView { 180 + fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 181 + div() 182 + // Make this element focusable 183 + .track_focus(&self.focus_handle) 184 + // Register action handlers 185 + .on_action(cx.listener(Self::handle_action)) 186 + } 187 + } 188 + ``` 189 + 190 + ## Dispatching Actions Programmatically 191 + 192 + ```rust 193 + // Dispatch to focused element 194 + window.dispatch_action(Box::new(MoveDown)); 195 + 196 + // Dispatch with data 197 + window.dispatch_action(Box::new(GoToLine { line: 42 })); 198 + ``` 199 + 200 + ## Action Availability 201 + 202 + Check if an action is available: 203 + 204 + ```rust 205 + if window.is_action_available(&MoveDown) { 206 + // Action has a handler in the current focus context 207 + } 208 + ``` 209 + 210 + ## Keyboard Navigation Pattern 211 + 212 + Common pattern for list navigation: 213 + 214 + ```rust 215 + struct ListView { 216 + items: Vec<Item>, 217 + selected: usize, 218 + focus_handle: FocusHandle, 219 + } 220 + 221 + impl Render for ListView { 222 + fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 223 + div() 224 + .track_focus(&self.focus_handle) 225 + .on_action(cx.listener(Self::select_prev)) 226 + .on_action(cx.listener(Self::select_next)) 227 + .on_action(cx.listener(Self::confirm_selection)) 228 + .children( 229 + self.items.iter().enumerate().map(|(i, item)| { 230 + self.render_item(i, item, cx) 231 + }) 232 + ) 233 + } 234 + } 235 + 236 + impl ListView { 237 + fn select_prev(&mut self, _: &SelectPrev, _window: &mut Window, cx: &mut Context<Self>) { 238 + self.selected = self.selected.saturating_sub(1); 239 + cx.notify(); 240 + } 241 + 242 + fn select_next(&mut self, _: &SelectNext, _window: &mut Window, cx: &mut Context<Self>) { 243 + if self.selected < self.items.len().saturating_sub(1) { 244 + self.selected += 1; 245 + } 246 + cx.notify(); 247 + } 248 + 249 + fn confirm_selection(&mut self, _: &Confirm, _window: &mut Window, cx: &mut Context<Self>) { 250 + if let Some(item) = self.items.get(self.selected) { 251 + cx.emit(ItemSelected(item.clone())); 252 + } 253 + } 254 + } 255 + ``` 256 + 257 + ## Input Method (IME) Handling 258 + 259 + For text input with IME support: 260 + 261 + ```rust 262 + impl InputHandler for MyEditor { 263 + fn text_for_range(&mut self, range: Range<usize>, cx: &mut Window) -> Option<String> { 264 + // Return text in range 265 + } 266 + 267 + fn selected_text_range(&mut self, ignore_disabled: bool, cx: &mut Window) -> Option<Range<usize>> { 268 + // Return current selection 269 + } 270 + 271 + fn replace_text_in_range( 272 + &mut self, 273 + range: Option<Range<usize>>, 274 + text: &str, 275 + cx: &mut Window, 276 + ) { 277 + // Insert/replace text 278 + } 279 + 280 + fn marked_text_range(&self, cx: &mut Window) -> Option<Range<usize>> { 281 + // Return IME composition range 282 + } 283 + 284 + // ... other methods 285 + } 286 + ```
+297
.claude/skills/gpui/styling.md
··· 1 + --- 2 + name: gpui-styling 3 + description: GPUI styling system - flexbox, colors, spacing, theming. Use when styling components, working with themes, or building layouts. 4 + --- 5 + 6 + # GPUI Styling 7 + 8 + GPUI uses a Tailwind-like builder pattern for styling via the `Styled` trait. 9 + 10 + ## Flexbox Layout 11 + 12 + ```rust 13 + // Horizontal layout 14 + div() 15 + .flex() 16 + .flex_row() // Default direction 17 + .items_center() // Cross-axis alignment 18 + .gap_2() // Gap between children 19 + 20 + // Vertical layout 21 + div() 22 + .flex() 23 + .flex_col() 24 + .gap_3() 25 + 26 + // Shorthand helpers (if available via StyledExt) 27 + h_flex() // flex + flex_row + items_center 28 + v_flex() // flex + flex_col 29 + ``` 30 + 31 + ### Main Axis Alignment (justify) 32 + 33 + ```rust 34 + .justify_start() // Pack at start 35 + .justify_center() // Center items 36 + .justify_end() // Pack at end 37 + .justify_between() // Space between items 38 + .justify_around() // Space around items 39 + .justify_evenly() // Equal spacing 40 + ``` 41 + 42 + ### Cross Axis Alignment (items/align) 43 + 44 + ```rust 45 + .items_start() // Align to start 46 + .items_center() // Center align 47 + .items_end() // Align to end 48 + .items_baseline() // Baseline align 49 + .items_stretch() // Stretch to fill 50 + ``` 51 + 52 + ### Flex Item Properties 53 + 54 + ```rust 55 + .flex_1() // Grow and shrink equally (flex: 1 1 0) 56 + .flex_auto() // Grow/shrink based on content 57 + .flex_initial() // Don't grow, can shrink 58 + .flex_none() // Don't grow or shrink 59 + .flex_grow() // Allow growing 60 + .flex_shrink() // Allow shrinking 61 + .flex_shrink_0() // Prevent shrinking 62 + ``` 63 + 64 + ## Sizing 65 + 66 + ```rust 67 + // Fixed sizes 68 + .w(px(200.0)) // Width in pixels 69 + .h(px(100.0)) // Height in pixels 70 + .size(px(50.0)) // Width and height 71 + 72 + // Relative sizes 73 + .w_full() // 100% width 74 + .h_full() // 100% height 75 + .size_full() // 100% both 76 + .w_half() // 50% width 77 + .min_w(px(100.0)) // Minimum width 78 + .max_w(px(500.0)) // Maximum width 79 + 80 + // Aspect ratio 81 + .aspect_ratio(16.0 / 9.0) 82 + ``` 83 + 84 + ## Spacing 85 + 86 + ### Padding 87 + 88 + ```rust 89 + .p(px(16.0)) // All sides 90 + .p_1() / .p_2() / .p_3() / .p_4() // Preset sizes 91 + .px_2() // Horizontal (left + right) 92 + .py_2() // Vertical (top + bottom) 93 + .pt_2() // Top only 94 + .pb_2() // Bottom only 95 + .pl_2() // Left only 96 + .pr_2() // Right only 97 + ``` 98 + 99 + ### Margin 100 + 101 + ```rust 102 + .m(px(8.0)) // All sides 103 + .m_1() / .m_2() / .m_3() / .m_4() 104 + .mx_auto() // Center horizontally 105 + .my_2() // Vertical margin 106 + .mt_2() / .mb_2() / .ml_2() / .mr_2() // Individual sides 107 + ``` 108 + 109 + ### Gap 110 + 111 + ```rust 112 + .gap(px(8.0)) // Gap between flex children 113 + .gap_1() / .gap_2() / .gap_3() / .gap_4() 114 + .gap_x(px(8.0)) // Horizontal gap only 115 + .gap_y(px(4.0)) // Vertical gap only 116 + ``` 117 + 118 + ## Colors and Backgrounds 119 + 120 + ```rust 121 + // Background 122 + .bg(hsla(0.0, 0.0, 0.2, 1.0)) // HSLA color 123 + .bg(rgb(0x1a1a1a)) // RGB hex 124 + .bg(cx.theme().colors().background) // Theme color 125 + 126 + // Text color (cascades to children) 127 + .text_color(hsla(0.0, 0.0, 1.0, 1.0)) 128 + .text_color(cx.theme().colors().text) 129 + 130 + // Opacity 131 + .opacity(0.5) 132 + ``` 133 + 134 + ## Borders 135 + 136 + ```rust 137 + .border_1() // 1px border all sides 138 + .border_2() // 2px border 139 + .border_t_1() // Top border only 140 + .border_b_1() // Bottom border only 141 + .border_l_1() // Left only 142 + .border_r_1() // Right only 143 + 144 + .border_color(hsla(0.0, 0.0, 0.3, 1.0)) 145 + .border_color(cx.theme().colors().border) 146 + ``` 147 + 148 + ## Corner Radius 149 + 150 + ```rust 151 + .rounded(px(4.0)) // Custom radius 152 + .rounded_sm() // Small 153 + .rounded_md() // Medium 154 + .rounded_lg() // Large 155 + .rounded_xl() // Extra large 156 + .rounded_full() // Fully rounded (pill shape) 157 + .rounded_none() // No rounding 158 + ``` 159 + 160 + ## Shadows 161 + 162 + ```rust 163 + .shadow_sm() // Small shadow 164 + .shadow_md() // Medium shadow 165 + .shadow_lg() // Large shadow 166 + .shadow_xl() // Extra large shadow 167 + ``` 168 + 169 + ## Text Styling 170 + 171 + ```rust 172 + // Size 173 + .text_xs() // 12px 174 + .text_sm() // 14px 175 + .text_base() // 16px 176 + .text_lg() // 18px 177 + .text_xl() // 20px 178 + .text_2xl() // 24px 179 + .text_size(px(18.0)) // Custom size 180 + 181 + // Weight 182 + .font_weight(FontWeight::BOLD) 183 + 184 + // Alignment 185 + .text_left() 186 + .text_center() 187 + .text_right() 188 + 189 + // Overflow 190 + .truncate() // Ellipsis with nowrap 191 + .line_clamp(2) // Max 2 lines 192 + .whitespace_nowrap() // No wrapping 193 + .overflow_hidden() // Hide overflow 194 + 195 + // Decoration 196 + .underline() 197 + .line_through() 198 + ``` 199 + 200 + ## Positioning 201 + 202 + ```rust 203 + .relative() // Position relative 204 + .absolute() // Position absolute 205 + 206 + // Inset (for absolute positioned elements) 207 + .top(px(10.0)) 208 + .bottom(px(10.0)) 209 + .left(px(10.0)) 210 + .right(px(10.0)) 211 + .inset(px(0.0)) // All sides 212 + ``` 213 + 214 + ## Overflow 215 + 216 + ```rust 217 + .overflow_hidden() // Clip overflow 218 + .overflow_scroll() // Scrollable 219 + .overflow_x_scroll() // Horizontal scroll 220 + .overflow_y_scroll() // Vertical scroll 221 + .overflow_visible() // Show overflow 222 + ``` 223 + 224 + ## Cursor 225 + 226 + ```rust 227 + .cursor_default() 228 + .cursor_pointer() 229 + .cursor_text() 230 + .cursor_move() 231 + .cursor_not_allowed() 232 + ``` 233 + 234 + ## Visibility 235 + 236 + ```rust 237 + .visible() 238 + .invisible() // Hidden but takes space 239 + ``` 240 + 241 + ## Theme Integration 242 + 243 + Access theme colors consistently: 244 + 245 + ```rust 246 + fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { 247 + let colors = cx.theme().colors(); 248 + 249 + div() 250 + .bg(colors.surface_background) 251 + .border_color(colors.border) 252 + .text_color(colors.text) 253 + .child("Themed content") 254 + } 255 + ``` 256 + 257 + ### Common Theme Colors 258 + 259 + ```rust 260 + colors.background // App background 261 + colors.surface_background // Surface/panel background 262 + colors.elevated_surface_background // Elevated elements 263 + colors.text // Primary text 264 + colors.text_muted // Secondary text 265 + colors.text_disabled // Disabled text 266 + colors.border // Primary borders 267 + colors.border_variant // Secondary borders 268 + colors.border_focused // Focused element border 269 + colors.element_background // Interactive element bg 270 + colors.element_hover // Hover state 271 + colors.element_active // Active/pressed state 272 + colors.element_selected // Selected state 273 + ``` 274 + 275 + ## Conditional Styling 276 + 277 + ```rust 278 + div() 279 + .when(is_selected, |this| { 280 + this.bg(cx.theme().colors().element_selected) 281 + }) 282 + .when(is_hovered, |this| { 283 + this.bg(cx.theme().colors().element_hover) 284 + }) 285 + .when_some(custom_color, |this, color| { 286 + this.bg(color) 287 + }) 288 + ``` 289 + 290 + ## Debug Helpers 291 + 292 + ```rust 293 + // Temporarily visualize layout 294 + .debug_bg_red() 295 + .debug_bg_green() 296 + .debug_bg_blue() 297 + ```
+436
.claude/skills/gpui/text-editing.md
··· 1 + --- 2 + name: gpui-text-editing 3 + description: GPUI text rendering, editing, and display mapping. Use when implementing text editors, syntax highlighting, or diff views. 4 + --- 5 + 6 + # GPUI Text Editing 7 + 8 + Patterns from Zed's editor for text rendering and editing. 9 + 10 + ## Text System Basics 11 + 12 + ### Shaping Text 13 + 14 + ```rust 15 + // Shape a line of text for rendering 16 + let line = window.text_system().shape_line( 17 + text.into(), // SharedString 18 + font_size, // Pixels 19 + &[TextRun { 20 + len: text.len(), 21 + font: font.clone(), 22 + color: text_color, 23 + background_color: None, 24 + underline: None, 25 + strikethrough: None, 26 + }], 27 + None, // wrap_width 28 + )?; 29 + 30 + // Get line dimensions 31 + let width = line.width; 32 + let ascent = line.ascent; 33 + let descent = line.descent; 34 + ``` 35 + 36 + ### Multiple Text Runs (Syntax Highlighting) 37 + 38 + ```rust 39 + // Build runs from highlight spans 40 + let mut runs = Vec::new(); 41 + let mut offset = 0; 42 + 43 + for (range, style) in highlights { 44 + if range.start > offset { 45 + // Unstyled gap 46 + runs.push(TextRun { 47 + len: range.start - offset, 48 + font: default_font.clone(), 49 + color: default_color, 50 + ..Default::default() 51 + }); 52 + } 53 + 54 + runs.push(TextRun { 55 + len: range.end - range.start, 56 + font: style.font.unwrap_or(default_font.clone()), 57 + color: style.color.unwrap_or(default_color), 58 + underline: style.underline, 59 + ..Default::default() 60 + }); 61 + 62 + offset = range.end; 63 + } 64 + 65 + let shaped = window.text_system().shape_line(text, font_size, &runs, None)?; 66 + ``` 67 + 68 + ### Line Wrapping 69 + 70 + ```rust 71 + // Create a line wrapper 72 + let mut wrapper = window.text_system().line_wrapper(font.clone(), font_size); 73 + 74 + // Wrap text to width 75 + let wrap_boundaries = wrapper.wrap_line(&text, wrap_width); 76 + // Returns indices where line breaks should occur 77 + ``` 78 + 79 + ## Display Mapping (Zed's Pattern) 80 + 81 + Zed uses layered transformations for display mapping: 82 + 83 + ``` 84 + Buffer Text 85 + ↓ InlayMap (insert inlay hints) 86 + ↓ FoldMap (collapse folded regions) 87 + ↓ TabMap (expand tabs to spaces) 88 + ↓ WrapMap (soft line wrapping) 89 + ↓ BlockMap (insert block decorations) 90 + Display Text 91 + ``` 92 + 93 + ### Basic Display Point Conversion 94 + 95 + ```rust 96 + // Buffer position to display position 97 + struct DisplayMap { 98 + buffer: Entity<Buffer>, 99 + wrap_width: Option<Pixels>, 100 + } 101 + 102 + impl DisplayMap { 103 + fn buffer_point_to_display(&self, point: Point, cx: &App) -> DisplayPoint { 104 + let buffer = self.buffer.read(cx); 105 + // Apply transformations... 106 + DisplayPoint { row, column } 107 + } 108 + 109 + fn display_point_to_buffer(&self, display: DisplayPoint, cx: &App) -> Point { 110 + // Reverse transformations... 111 + Point { row, column } 112 + } 113 + } 114 + 115 + // Display point for rendering 116 + #[derive(Clone, Copy)] 117 + struct DisplayPoint { 118 + row: u32, 119 + column: u32, 120 + } 121 + ``` 122 + 123 + ## Selection Rendering 124 + 125 + ```rust 126 + struct SelectionLayout { 127 + range: Range<DisplayPoint>, 128 + head: DisplayPoint, // Cursor position 129 + is_newest: bool, // Primary selection 130 + cursor_shape: CursorShape, 131 + } 132 + 133 + impl SelectionLayout { 134 + fn from_selection( 135 + selection: &Selection, 136 + map: &DisplayMap, 137 + cx: &App, 138 + ) -> Self { 139 + let start = map.buffer_point_to_display(selection.start, cx); 140 + let end = map.buffer_point_to_display(selection.end, cx); 141 + 142 + Self { 143 + range: start..end, 144 + head: if selection.reversed { start } else { end }, 145 + is_newest: selection.is_primary, 146 + cursor_shape: CursorShape::Bar, 147 + } 148 + } 149 + } 150 + 151 + // Render selection highlight 152 + fn paint_selection( 153 + &self, 154 + selection: &SelectionLayout, 155 + line_height: Pixels, 156 + window: &mut Window, 157 + cx: &App, 158 + ) { 159 + let color = cx.theme().colors().selection; 160 + 161 + for row in selection.range.start.row..=selection.range.end.row { 162 + let start_x = if row == selection.range.start.row { 163 + self.x_for_column(row, selection.range.start.column) 164 + } else { 165 + Pixels::ZERO 166 + }; 167 + 168 + let end_x = if row == selection.range.end.row { 169 + self.x_for_column(row, selection.range.end.column) 170 + } else { 171 + self.line_width(row) 172 + }; 173 + 174 + let y = self.y_for_row(row); 175 + let bounds = Bounds { 176 + origin: point(start_x, y), 177 + size: size(end_x - start_x, line_height), 178 + }; 179 + 180 + window.paint_quad(fill(bounds, color)); 181 + } 182 + } 183 + ``` 184 + 185 + ## Cursor Rendering 186 + 187 + ```rust 188 + enum CursorShape { 189 + Bar, 190 + Block, 191 + Underline, 192 + Hollow, 193 + } 194 + 195 + fn paint_cursor( 196 + &self, 197 + position: DisplayPoint, 198 + shape: CursorShape, 199 + color: Hsla, 200 + line_height: Pixels, 201 + window: &mut Window, 202 + ) { 203 + let x = self.x_for_column(position.row, position.column); 204 + let y = self.y_for_row(position.row); 205 + 206 + let bounds = match shape { 207 + CursorShape::Bar => Bounds { 208 + origin: point(x, y), 209 + size: size(px(2.0), line_height), 210 + }, 211 + CursorShape::Block => Bounds { 212 + origin: point(x, y), 213 + size: size(self.char_width, line_height), 214 + }, 215 + CursorShape::Underline => Bounds { 216 + origin: point(x, y + line_height - px(2.0)), 217 + size: size(self.char_width, px(2.0)), 218 + }, 219 + CursorShape::Hollow => { 220 + // Paint outline 221 + let bounds = Bounds { 222 + origin: point(x, y), 223 + size: size(self.char_width, line_height), 224 + }; 225 + window.paint_quad(outline(bounds, color)); 226 + return; 227 + } 228 + }; 229 + 230 + window.paint_quad(fill(bounds, color)); 231 + } 232 + ``` 233 + 234 + ## Diff Hunks 235 + 236 + ```rust 237 + #[derive(Clone)] 238 + enum DiffHunkStatus { 239 + Added, 240 + Modified, 241 + Deleted, 242 + } 243 + 244 + struct DiffHunk { 245 + buffer_range: Range<Point>, 246 + status: DiffHunkStatus, 247 + } 248 + 249 + // Render diff indicators in gutter 250 + fn paint_diff_hunks( 251 + &self, 252 + hunks: &[DiffHunk], 253 + gutter_width: Pixels, 254 + line_height: Pixels, 255 + window: &mut Window, 256 + cx: &App, 257 + ) { 258 + let colors = cx.theme().colors(); 259 + 260 + for hunk in hunks { 261 + let color = match hunk.status { 262 + DiffHunkStatus::Added => colors.created, 263 + DiffHunkStatus::Modified => colors.modified, 264 + DiffHunkStatus::Deleted => colors.deleted, 265 + }; 266 + 267 + let start_y = self.y_for_row(hunk.buffer_range.start.row); 268 + let end_y = self.y_for_row(hunk.buffer_range.end.row); 269 + let height = end_y - start_y + line_height; 270 + 271 + let indicator_bounds = Bounds { 272 + origin: point(gutter_width - px(3.0), start_y), 273 + size: size(px(2.0), height), 274 + }; 275 + 276 + window.paint_quad(fill(indicator_bounds, color)); 277 + } 278 + } 279 + ``` 280 + 281 + ## Inline Diagnostics 282 + 283 + ```rust 284 + struct Diagnostic { 285 + range: Range<Point>, 286 + severity: DiagnosticSeverity, 287 + message: String, 288 + } 289 + 290 + enum DiagnosticSeverity { 291 + Error, 292 + Warning, 293 + Info, 294 + Hint, 295 + } 296 + 297 + // Underline diagnostic ranges 298 + fn paint_diagnostic_underline( 299 + &self, 300 + diagnostic: &Diagnostic, 301 + window: &mut Window, 302 + cx: &App, 303 + ) { 304 + let color = match diagnostic.severity { 305 + DiagnosticSeverity::Error => cx.theme().colors().error, 306 + DiagnosticSeverity::Warning => cx.theme().colors().warning, 307 + DiagnosticSeverity::Info => cx.theme().colors().info, 308 + DiagnosticSeverity::Hint => cx.theme().colors().hint, 309 + }; 310 + 311 + // Paint wavy underline for error range 312 + let start = self.point_to_pixel(diagnostic.range.start); 313 + let end = self.point_to_pixel(diagnostic.range.end); 314 + 315 + // Use wavy line path or simple underline 316 + window.paint_underline(start, end, color, UnderlineStyle::Wavy); 317 + } 318 + ``` 319 + 320 + ## Gutter Elements 321 + 322 + ```rust 323 + fn render_gutter( 324 + &self, 325 + visible_rows: Range<u32>, 326 + window: &mut Window, 327 + cx: &mut App, 328 + ) -> impl IntoElement { 329 + let line_height = self.line_height; 330 + let gutter_width = self.gutter_width; 331 + 332 + div() 333 + .w(gutter_width) 334 + .h_full() 335 + .flex() 336 + .flex_col() 337 + .children(visible_rows.map(|row| { 338 + self.render_gutter_row(row, line_height, cx) 339 + })) 340 + } 341 + 342 + fn render_gutter_row( 343 + &self, 344 + row: u32, 345 + line_height: Pixels, 346 + cx: &App, 347 + ) -> impl IntoElement { 348 + let line_number = row + 1; 349 + let is_current = row == self.cursor_row; 350 + 351 + div() 352 + .h(line_height) 353 + .w_full() 354 + .flex() 355 + .items_center() 356 + .justify_end() 357 + .pr_2() 358 + .text_color(if is_current { 359 + cx.theme().colors().text 360 + } else { 361 + cx.theme().colors().text_muted 362 + }) 363 + .child(format!("{}", line_number)) 364 + } 365 + ``` 366 + 367 + ## Movement Calculations 368 + 369 + ```rust 370 + impl Editor { 371 + fn move_up(&mut self, cx: &mut Context<Self>) { 372 + self.move_cursors(|cursor, map| { 373 + let current = map.buffer_point_to_display(cursor.position); 374 + if current.row > 0 { 375 + let new_display = DisplayPoint { 376 + row: current.row - 1, 377 + column: cursor.goal_column.unwrap_or(current.column), 378 + }; 379 + let new_buffer = map.display_point_to_buffer(new_display); 380 + cursor.position = new_buffer; 381 + } 382 + }, cx); 383 + } 384 + 385 + fn move_to_line_start(&mut self, cx: &mut Context<Self>) { 386 + self.move_cursors(|cursor, map| { 387 + cursor.position.column = 0; 388 + cursor.goal_column = None; 389 + }, cx); 390 + } 391 + 392 + fn move_to_line_end(&mut self, cx: &mut Context<Self>) { 393 + self.move_cursors(|cursor, map| { 394 + let line_len = map.line_len(cursor.position.row); 395 + cursor.position.column = line_len; 396 + cursor.goal_column = None; 397 + }, cx); 398 + } 399 + } 400 + ``` 401 + 402 + ## Virtual Scrolling for Large Files 403 + 404 + ```rust 405 + struct EditorElement { 406 + scroll_position: Point<Pixels>, 407 + visible_row_range: Range<u32>, 408 + } 409 + 410 + impl EditorElement { 411 + fn calculate_visible_rows( 412 + &self, 413 + viewport_height: Pixels, 414 + line_height: Pixels, 415 + total_rows: u32, 416 + ) -> Range<u32> { 417 + let scroll_top = self.scroll_position.y; 418 + let first_visible = (scroll_top / line_height).floor() as u32; 419 + let visible_count = (viewport_height / line_height).ceil() as u32 + 1; 420 + let last_visible = (first_visible + visible_count).min(total_rows); 421 + 422 + first_visible..last_visible 423 + } 424 + 425 + fn render_visible_lines( 426 + &self, 427 + visible_rows: Range<u32>, 428 + window: &mut Window, 429 + cx: &mut App, 430 + ) -> Vec<impl IntoElement> { 431 + visible_rows 432 + .map(|row| self.render_line(row, window, cx)) 433 + .collect() 434 + } 435 + } 436 + ```