commits
- Replace text arrows (▲/▼) with Icons::chevron_down() SVG icon
- Add rotation transform when dropdown is open (180 degrees via percentage(0.5))
- Reduce font size from text_sm to text_xs for both trigger and menu options
- Icon is 12px and uses fg_muted color for subtle appearance
Note: gpui::percentage() expects 0-1 range, not 0-100. The API panics at runtime
rather than failing at compile time since Rust can't express bounded floats in types.
Extracted and adapted the dropdown component from gpui-input-sandbox with the following changes:
- Created elements/dropdown.rs with type-safe generic Dropdown<T> component
- Applied gpuikit theming using Themeable trait (replaced hardcoded rgb colors)
- Added proper documentation with usage examples
- Exported dropdown module from elements.rs
- Added dropdown demo to showcase app with two examples (Size and Priority enums)
The dropdown consists of:
- dropdown() convenience function for creating dropdown builders
- Dropdown<T> builder struct for configuration
- DropdownState<T> entity that manages the menu popup and selection state
- DropdownMenu internal popup component
- DropdownChanged event for reacting to selection changes
API features:
- on_change() callback for handling selection changes
- full_width() option for expanded layout
- set_selected() for programmatic updates
- is_open() to check menu state
- Generic T parameter enables type-safe option values
- Changed on_toggle to accept &bool for cx.listener() compatibility
- Showcase now demonstrates counting toggled buttons by using
cx.listener to update view state when buttons are toggled
Example usage:
icon_button("toggle-star", DefaultIcons::star())
.use_state()
.on_toggle(cx.listener(|view, toggled, _window, cx| {
if *toggled {
view.count += 1;
} else {
view.count -= 1;
}
cx.notify();
}))
use_state() uses caller location to generate state IDs, causing all
IconButton instances to share the same toggle state. Switched to
use_keyed_state() which uses the button's ElementId to ensure each
button has its own isolated state.
Implements internal state management for IconButton using gpui's
Window::use_state API, which persists Entity state across frames.
API:
- .use_state() - Enable internal toggle state tracking
- .on_toggle(callback) - Called when toggle state changes
- .selected(bool) - External control (takes precedence over internal state)
The internal state uses gpui's element state system, which automatically
manages Entity lifecycle based on whether the element is rendered in
consecutive frames.
Example usage:
icon_button("toggle-star", DefaultIcons::star())
.use_state()
.on_toggle(|toggled, window, cx| {
println!("Toggled: {}", toggled);
})
The existing .selected(bool) API still works for external state control
and takes precedence over internal state when both are used.
ResourceSource<T> implements AssetSource, not the RustEmbed type directly.
Added gpuikit::assets() function that returns the properly wrapped asset source.
Assets must be set on Application before .run(), not during init().
- Export Assets struct (RustEmbed for assets/ folder)
- Update showcase to use Application::new().with_assets(gpuikit::Assets)
- Add documentation showing proper initialization pattern
Show a row of different icons (star, heart, gear, bell, home, search,
plus, trash) plus examples of selected and disabled states.
Exposes all 347 Radix icons via the Icons struct with methods like:
- DefaultIcons::star()
- DefaultIcons::chevron_down()
- DefaultIcons::magnifying_glass()
- etc.
Each method returns an Svg that can be passed to icon_button or used
directly. Updated showcase to use DefaultIcons instead of raw paths.
A simple icon button that takes an Svg and provides:
- selected state (accent color)
- disabled state (muted color, no interactions)
- hover state (surface_secondary background)
- on_click handler
Added to showcase with examples of all state combinations.
Introduces a trait-based theme system that allows consumers to implement
their own theme types while maintaining compatibility with gpuikit components.
The Themeable trait defines:
- 5 required primitive colors: fg, bg, surface, border, accent
- ~15 optional colors with sensible defaults derived from primitives
Components now use trait methods (theme.fg()) instead of field access
(theme.fg), making them work with any type that implements Themeable.
The concrete Theme struct implements Themeable and stores optional overrides
for fine-grained control when needed.
Removed the Themes collection manager as it was unused.
The initial implementation tried to handle mouse clicks within the EditorElement itself,
but this approach had critical issues:
1. The EditorElement didn't have access to the view context needed for cx.notify()
2. The canvas element was likely blocking mouse events from propagating
3. Without notify(), the view wasn't rerendering after cursor position changes
Solution:
- Moved mouse click handling to the EditorView level in the demo
- Uses cx.listener() to properly access the view's context
- Can now call cx.notify() to trigger a re-render after cursor moves
- Also focuses the editor when clicked
The click handler:
- Calculates the clicked position relative to the editor bounds
- Accounts for gutter width and scroll offset
- Converts pixel coordinates to row/column positions
- Clamps positions to valid buffer bounds
- Updates cursor position and ensures it's visible
This properly implements click-to-position functionality that actually works.
- Refactored EditorElement to use canvas for custom rendering
- Added mouse click handler that calculates cursor position from click coordinates
- Accounts for scroll offset, gutter width, and line height in position calculation
- Uses rough character width approximation for column calculation
- Clears selection when clicking
- Ensures cursor is visible after repositioning
This completes the 'Update mouse click position calculation to account for scroll offset' item from the scrolling todo list. The implementation wraps the editor rendering in a div with a mouse_down handler that calculates the appropriate cursor position based on the click location relative to the editor bounds.
- Added page_up() and page_down() methods to scroll and move cursor by one page
- Added move_to_line_start() and move_to_line_end() for Home/End key behavior
- Added move_to_document_start() and move_to_document_end() for Ctrl+Home/Ctrl+End
- All navigation methods support both regular and shift-modified versions for selection
- Updated editor demo with action handlers for all new navigation shortcuts
- Added comprehensive keybindings in demo-keymap.json
- Added unit tests to verify all navigation behaviors
This completes three major items from the scrolling todo list:
- PageUp/PageDown keyboard shortcuts
- Home/End keyboard shortcuts
- Ctrl+Home/Ctrl+End for document start/end
The implementation follows the existing pattern for cursor movement with proper selection handling and scroll position updates.
- Implemented InteractiveElement trait for EditorElement
- Added mouse wheel event handler with pixel/line scroll support
- Added ensure_cursor_visible() to auto-scroll when cursor moves
- Auto-scroll maintains 3-line margin above/below cursor
- Added comprehensive tests for auto-scroll behavior
- Added scroll_row state tracking to Editor
- Modified paint_lines() to only render visible rows based on viewport
- Updated all painting methods to account for scroll offset
- Added comprehensive scrolling tests
- Created scrolling-todo.md checklist for remaining work
All rows maintain exactly LINE_HEIGHT, no scrollbars rendered.
This test verifies that when moving up/down through lines of different lengths, the editor maintains the original column position (goal column) and returns to it when reaching a line that's long enough. The goal column is reset when moving horizontally.
Improve clipboard tests to use actual clipboard operations
- Removed test_cursor_position_after_paste_simulation (duplicate)
- Fixed test_cursor_position_after_paste to use TestAppContext correctly
- Added test_copy_and_paste_selection to test copy operation
- Added test_cut_and_paste to test cut operation
- Removed unused imports
These tests now properly use GPUI's clipboard instead of simulating it.
Update TODO.md with all tests fixed in current session
Fixed 17 tests total:
- 9 tests with exact assertions replacing vague contains/comparisons
- 8 tests with proper behavior verification
Only 7 tests remain with compromised assertions:
- 3 Unicode tests with incomplete checks
- 4 placeholder/syntax highlighting tests with no real assertions
Fix tests with no assertions or accepting multiple outcomes
- test_selection_with_zero_width_joiner: Now properly tests ZWJ emoji insertion and selection
- test_normalization_inconsistencies: Now documents that normalization doesn't happen
Both tests now verify actual behavior instead of just "not crashing" or accepting any outcome.
Fix test_windows_line_endings to document actual CRLF behavior
Test now correctly asserts that the buffer:
- Only treats \n as line separator (not \r\n as a unit)
- Preserves \r as part of line content
- "Line1\r\n" becomes two lines: "Line1\r" and ""
This reveals that CRLF is not properly handled, which is documented in TODO as a missing feature.
Fix more tests with exact assertions
- test_selection_with_mixed_line_endings: Now asserts exact selection " 1\nLine 2"
- test_zero_width_characters: Now asserts exact string "a\u{200B}b" and cursor position
These tests were using vague contains() checks instead of verifying exact behavior.
Fix more tests to use exact assertions
- test_mixed_width_characters: Now asserts exact string "AA😀B" and cursor position
- test_empty_selection: Now asserts selection is empty "" after collapse
Removed vague assertions that used contains() or <= comparisons.
Fix selection tests to use exact assertions
- test_select_across_lines: Now asserts exact selection "st\nSecon" instead of vague contains
- test_select_emoji: Now asserts exact selection of emoji and verifies cursor positions
Both tests were using contains() which didn't verify the exact selection behavior.
Document all tests with compromised assertions in TODO.md
Found 16 tests still needing fixes:
- 4 selection tests using vague 'contains' assertions
- 5 Unicode/character tests with incomplete checks
- 1 line ending test accepting any result >= 1
- 1 normalization test accepting either outcome with ||
- 5 tests with no meaningful assertions at all
This gives a clear picture of remaining test quality issues to address.
Update TODO.md with fixed tests from current session
Documented tests that have been fixed:
- Cursor clamping now works properly
- Cursor wrapping was already working, tests now verify
- Selection deletion works, tests now verify
- Control character handling preserves all chars
- Several tests with vague assertions now have exact assertions
Updated priorities to focus on remaining tests with compromised assertions.
Fix test_control_characters to assert exact behavior
- Test now correctly asserts that all 4 control characters are preserved
- Verifies each character is at its expected position
- Asserts cursor is at position 4 after inserting 4 characters
The previous test used '<=' which didn't specify whether control chars should be filtered or preserved. The implementation preserves all characters, which this test now properly verifies.
Fix test_consecutive_emoji_deletion assertions
- Test now correctly asserts that backspace at position 3 deletes the 3rd emoji (👌)
- Asserts exact result "👍👎" instead of accepting either 👍 or 👌 remaining
- Asserts cursor position after deletion is at position 2
The previous assertion was nonsensical, accepting two different incorrect outcomes.
Fix test_cursor_position_after_bulk_delete assertions
- Test now properly asserts that selecting chars 4-7 ("4567") and deleting removes exactly those chars
- Asserts resulting text is "123890" (not "1230" which was incorrectly accepted before)
- Asserts cursor position is at deletion point (position 3)
The test was accepting two completely different outcomes, hiding whether selection deletion worked properly.
Add proper assertions to cursor wrapping tests
- test_cursor_wrapping_to_next_line now asserts wrap from end of line to start of next
- test_cursor_wrapping_to_previous_line now asserts wrap from start of line to end of previous
- Both tests pass - the functionality was already correctly implemented in move_left/move_right
These tests previously had no assertions, hiding whether the feature worked or not.
Fix cursor position clamping to valid bounds
- Added clamp_cursor_position() method to ensure cursor stays within valid bounds
- set_cursor_position() now clamps position to [0, line_count-1] for row and [0, line_len] for column
- Fixed test_cursor_clamping_to_valid_position to assert correct behavior (clamp to end of line)
- Documented that test_cursor_clamping_negative_values is unnecessary due to usize type safety
The previous test was accepting invalid positions as correct, which hid a serious bug where
cursor could be set to positions outside the buffer.
keep fixing tests
wip
Add test for goal column behavior with selections
This test verifies the complex interaction between goal column and selections:
- Goal column is maintained when moving vertically with shift (selection)
- Moving horizontally resets the goal column
- Clearing selection resets goal column
- Goal column persists appropriately through selection operations
- Moving without shift after selection uses the maintained goal column
Add test for empty buffer and edge cases
This test verifies:
- Empty buffer is treated as having one empty line (common editor convention)
- Operations on empty buffer don't crash (backspace, delete, movement)
- Inserting into empty buffer works correctly
- Single character document edge cases
- Navigation through multiple empty lines
- Select all on empty content
- Very long lines (10000 characters) work correctly
- Out-of-bounds cursor positions are handled
Add test for syntax highlighting state management
This test verifies:
- Language auto-detection works
- Theme switching updates configuration colors
- Language can be changed dynamically
- Updating lines clears highlighting state appropriately
- Buffer updates reset highlighting
- Text modifications clear highlighting from modified line onward
- Syntax highlighting produces text runs with styling
Add test for cursor positioning at line boundaries
This test verifies cursor behavior when:
- Backspacing at the beginning of a line (merges with previous)
- Deleting at the end of a line (merges with next)
- Working with empty lines
- Cursor position is clamped when line content changes
Note: Found that backspace at line start places cursor at end of merged line,
not at the merge point - this may be unexpected behavior worth revisiting.
Add test for text replacement over selection
This test verifies that when text is selected:
- Inserting a character replaces the selection
- Backspace deletes the selection
- Delete removes the selection
- Newline replaces the selection
- Multi-line selections are correctly replaced with typed text
Add test for selection behavior with shift+movement
This test verifies that:
- Shift+arrow keys create and extend selections
- Moving without shift clears the selection
- Selection works in all directions (left, right, up, down)
- get_selected_text() returns the correct text
- Multi-line selections work correctly across line boundaries
- Replace text arrows (▲/▼) with Icons::chevron_down() SVG icon
- Add rotation transform when dropdown is open (180 degrees via percentage(0.5))
- Reduce font size from text_sm to text_xs for both trigger and menu options
- Icon is 12px and uses fg_muted color for subtle appearance
Note: gpui::percentage() expects 0-1 range, not 0-100. The API panics at runtime
rather than failing at compile time since Rust can't express bounded floats in types.
Extracted and adapted the dropdown component from gpui-input-sandbox with the following changes:
- Created elements/dropdown.rs with type-safe generic Dropdown<T> component
- Applied gpuikit theming using Themeable trait (replaced hardcoded rgb colors)
- Added proper documentation with usage examples
- Exported dropdown module from elements.rs
- Added dropdown demo to showcase app with two examples (Size and Priority enums)
The dropdown consists of:
- dropdown() convenience function for creating dropdown builders
- Dropdown<T> builder struct for configuration
- DropdownState<T> entity that manages the menu popup and selection state
- DropdownMenu internal popup component
- DropdownChanged event for reacting to selection changes
API features:
- on_change() callback for handling selection changes
- full_width() option for expanded layout
- set_selected() for programmatic updates
- is_open() to check menu state
- Generic T parameter enables type-safe option values
- Changed on_toggle to accept &bool for cx.listener() compatibility
- Showcase now demonstrates counting toggled buttons by using
cx.listener to update view state when buttons are toggled
Example usage:
icon_button("toggle-star", DefaultIcons::star())
.use_state()
.on_toggle(cx.listener(|view, toggled, _window, cx| {
if *toggled {
view.count += 1;
} else {
view.count -= 1;
}
cx.notify();
}))
Implements internal state management for IconButton using gpui's
Window::use_state API, which persists Entity state across frames.
API:
- .use_state() - Enable internal toggle state tracking
- .on_toggle(callback) - Called when toggle state changes
- .selected(bool) - External control (takes precedence over internal state)
The internal state uses gpui's element state system, which automatically
manages Entity lifecycle based on whether the element is rendered in
consecutive frames.
Example usage:
icon_button("toggle-star", DefaultIcons::star())
.use_state()
.on_toggle(|toggled, window, cx| {
println!("Toggled: {}", toggled);
})
The existing .selected(bool) API still works for external state control
and takes precedence over internal state when both are used.
Introduces a trait-based theme system that allows consumers to implement
their own theme types while maintaining compatibility with gpuikit components.
The Themeable trait defines:
- 5 required primitive colors: fg, bg, surface, border, accent
- ~15 optional colors with sensible defaults derived from primitives
Components now use trait methods (theme.fg()) instead of field access
(theme.fg), making them work with any type that implements Themeable.
The concrete Theme struct implements Themeable and stores optional overrides
for fine-grained control when needed.
Removed the Themes collection manager as it was unused.
The initial implementation tried to handle mouse clicks within the EditorElement itself,
but this approach had critical issues:
1. The EditorElement didn't have access to the view context needed for cx.notify()
2. The canvas element was likely blocking mouse events from propagating
3. Without notify(), the view wasn't rerendering after cursor position changes
Solution:
- Moved mouse click handling to the EditorView level in the demo
- Uses cx.listener() to properly access the view's context
- Can now call cx.notify() to trigger a re-render after cursor moves
- Also focuses the editor when clicked
The click handler:
- Calculates the clicked position relative to the editor bounds
- Accounts for gutter width and scroll offset
- Converts pixel coordinates to row/column positions
- Clamps positions to valid buffer bounds
- Updates cursor position and ensures it's visible
This properly implements click-to-position functionality that actually works.
- Refactored EditorElement to use canvas for custom rendering
- Added mouse click handler that calculates cursor position from click coordinates
- Accounts for scroll offset, gutter width, and line height in position calculation
- Uses rough character width approximation for column calculation
- Clears selection when clicking
- Ensures cursor is visible after repositioning
This completes the 'Update mouse click position calculation to account for scroll offset' item from the scrolling todo list. The implementation wraps the editor rendering in a div with a mouse_down handler that calculates the appropriate cursor position based on the click location relative to the editor bounds.
- Added page_up() and page_down() methods to scroll and move cursor by one page
- Added move_to_line_start() and move_to_line_end() for Home/End key behavior
- Added move_to_document_start() and move_to_document_end() for Ctrl+Home/Ctrl+End
- All navigation methods support both regular and shift-modified versions for selection
- Updated editor demo with action handlers for all new navigation shortcuts
- Added comprehensive keybindings in demo-keymap.json
- Added unit tests to verify all navigation behaviors
This completes three major items from the scrolling todo list:
- PageUp/PageDown keyboard shortcuts
- Home/End keyboard shortcuts
- Ctrl+Home/Ctrl+End for document start/end
The implementation follows the existing pattern for cursor movement with proper selection handling and scroll position updates.
- Added scroll_row state tracking to Editor
- Modified paint_lines() to only render visible rows based on viewport
- Updated all painting methods to account for scroll offset
- Added comprehensive scrolling tests
- Created scrolling-todo.md checklist for remaining work
All rows maintain exactly LINE_HEIGHT, no scrollbars rendered.
This test verifies that when moving up/down through lines of different lengths, the editor maintains the original column position (goal column) and returns to it when reaching a line that's long enough. The goal column is reset when moving horizontally.
Improve clipboard tests to use actual clipboard operations
- Removed test_cursor_position_after_paste_simulation (duplicate)
- Fixed test_cursor_position_after_paste to use TestAppContext correctly
- Added test_copy_and_paste_selection to test copy operation
- Added test_cut_and_paste to test cut operation
- Removed unused imports
These tests now properly use GPUI's clipboard instead of simulating it.
Update TODO.md with all tests fixed in current session
Fixed 17 tests total:
- 9 tests with exact assertions replacing vague contains/comparisons
- 8 tests with proper behavior verification
Only 7 tests remain with compromised assertions:
- 3 Unicode tests with incomplete checks
- 4 placeholder/syntax highlighting tests with no real assertions
Fix tests with no assertions or accepting multiple outcomes
- test_selection_with_zero_width_joiner: Now properly tests ZWJ emoji insertion and selection
- test_normalization_inconsistencies: Now documents that normalization doesn't happen
Both tests now verify actual behavior instead of just "not crashing" or accepting any outcome.
Fix test_windows_line_endings to document actual CRLF behavior
Test now correctly asserts that the buffer:
- Only treats \n as line separator (not \r\n as a unit)
- Preserves \r as part of line content
- "Line1\r\n" becomes two lines: "Line1\r" and ""
This reveals that CRLF is not properly handled, which is documented in TODO as a missing feature.
Fix more tests with exact assertions
- test_selection_with_mixed_line_endings: Now asserts exact selection " 1\nLine 2"
- test_zero_width_characters: Now asserts exact string "a\u{200B}b" and cursor position
These tests were using vague contains() checks instead of verifying exact behavior.
Fix more tests to use exact assertions
- test_mixed_width_characters: Now asserts exact string "AA😀B" and cursor position
- test_empty_selection: Now asserts selection is empty "" after collapse
Removed vague assertions that used contains() or <= comparisons.
Fix selection tests to use exact assertions
- test_select_across_lines: Now asserts exact selection "st\nSecon" instead of vague contains
- test_select_emoji: Now asserts exact selection of emoji and verifies cursor positions
Both tests were using contains() which didn't verify the exact selection behavior.
Document all tests with compromised assertions in TODO.md
Found 16 tests still needing fixes:
- 4 selection tests using vague 'contains' assertions
- 5 Unicode/character tests with incomplete checks
- 1 line ending test accepting any result >= 1
- 1 normalization test accepting either outcome with ||
- 5 tests with no meaningful assertions at all
This gives a clear picture of remaining test quality issues to address.
Update TODO.md with fixed tests from current session
Documented tests that have been fixed:
- Cursor clamping now works properly
- Cursor wrapping was already working, tests now verify
- Selection deletion works, tests now verify
- Control character handling preserves all chars
- Several tests with vague assertions now have exact assertions
Updated priorities to focus on remaining tests with compromised assertions.
Fix test_control_characters to assert exact behavior
- Test now correctly asserts that all 4 control characters are preserved
- Verifies each character is at its expected position
- Asserts cursor is at position 4 after inserting 4 characters
The previous test used '<=' which didn't specify whether control chars should be filtered or preserved. The implementation preserves all characters, which this test now properly verifies.
Fix test_consecutive_emoji_deletion assertions
- Test now correctly asserts that backspace at position 3 deletes the 3rd emoji (👌)
- Asserts exact result "👍👎" instead of accepting either 👍 or 👌 remaining
- Asserts cursor position after deletion is at position 2
The previous assertion was nonsensical, accepting two different incorrect outcomes.
Fix test_cursor_position_after_bulk_delete assertions
- Test now properly asserts that selecting chars 4-7 ("4567") and deleting removes exactly those chars
- Asserts resulting text is "123890" (not "1230" which was incorrectly accepted before)
- Asserts cursor position is at deletion point (position 3)
The test was accepting two completely different outcomes, hiding whether selection deletion worked properly.
Add proper assertions to cursor wrapping tests
- test_cursor_wrapping_to_next_line now asserts wrap from end of line to start of next
- test_cursor_wrapping_to_previous_line now asserts wrap from start of line to end of previous
- Both tests pass - the functionality was already correctly implemented in move_left/move_right
These tests previously had no assertions, hiding whether the feature worked or not.
Fix cursor position clamping to valid bounds
- Added clamp_cursor_position() method to ensure cursor stays within valid bounds
- set_cursor_position() now clamps position to [0, line_count-1] for row and [0, line_len] for column
- Fixed test_cursor_clamping_to_valid_position to assert correct behavior (clamp to end of line)
- Documented that test_cursor_clamping_negative_values is unnecessary due to usize type safety
The previous test was accepting invalid positions as correct, which hid a serious bug where
cursor could be set to positions outside the buffer.
keep fixing tests
wip
Add test for goal column behavior with selections
This test verifies the complex interaction between goal column and selections:
- Goal column is maintained when moving vertically with shift (selection)
- Moving horizontally resets the goal column
- Clearing selection resets goal column
- Goal column persists appropriately through selection operations
- Moving without shift after selection uses the maintained goal column
Add test for empty buffer and edge cases
This test verifies:
- Empty buffer is treated as having one empty line (common editor convention)
- Operations on empty buffer don't crash (backspace, delete, movement)
- Inserting into empty buffer works correctly
- Single character document edge cases
- Navigation through multiple empty lines
- Select all on empty content
- Very long lines (10000 characters) work correctly
- Out-of-bounds cursor positions are handled
Add test for syntax highlighting state management
This test verifies:
- Language auto-detection works
- Theme switching updates configuration colors
- Language can be changed dynamically
- Updating lines clears highlighting state appropriately
- Buffer updates reset highlighting
- Text modifications clear highlighting from modified line onward
- Syntax highlighting produces text runs with styling
Add test for cursor positioning at line boundaries
This test verifies cursor behavior when:
- Backspacing at the beginning of a line (merges with previous)
- Deleting at the end of a line (merges with next)
- Working with empty lines
- Cursor position is clamped when line content changes
Note: Found that backspace at line start places cursor at end of merged line,
not at the merge point - this may be unexpected behavior worth revisiting.
Add test for text replacement over selection
This test verifies that when text is selected:
- Inserting a character replaces the selection
- Backspace deletes the selection
- Delete removes the selection
- Newline replaces the selection
- Multi-line selections are correctly replaced with typed text
Add test for selection behavior with shift+movement
This test verifies that:
- Shift+arrow keys create and extend selections
- Moving without shift clears the selection
- Selection works in all directions (left, right, up, down)
- get_selected_text() returns the correct text
- Multi-line selections work correctly across line boundaries