Skip to content

Conversation

schlich
Copy link

@schlich schlich commented Oct 10, 2025

Add Helix keybinding mode

Summary

This PR adds a new Helix-style modal editing mode to Reedline, providing an alternative to the existing Vi and Emacs modes. Helix mode implements selection-extends-on-motion (Helix's signature feature) along with character/word/line motions, find/till motions, select mode toggle, and editing commands.

This is a purely additive change with zero impact on existing functionality. The implementation follows the same architectural patterns as Vi and Emacs modes, integrating cleanly into the existing EditMode trait system.

Features

Normal Mode (default starting mode)

Mode switching:

  • v - Toggle select mode
  • i/a/I/A - Enter insert at cursor/after/line start/line end

Character motions (extend selection):

  • h/l - Move left/right with selection

Word motions (extend selection):

  • w - Next word start
  • b - Previous word start
  • e - Word end
  • W - Next WORD start (whitespace-delimited)
  • B - Previous WORD start
  • E - WORD end

Line motions (extend selection):

  • 0 - Line start
  • $ - Line end

Find/till motions (extend selection):

  • f{char} - Find next occurrence of character
  • t{char} - Till next occurrence (stop before)
  • F{char} - Find previous occurrence
  • T{char} - Till previous occurrence (stop after)

Selection commands:

  • x - Select entire line
  • ; - Collapse selection to cursor
  • Alt+; - Swap cursor and anchor (flip selection)

Edit commands:

  • d - Delete selection
  • c - Change selection (delete and enter insert)
  • y - Yank/copy selection
  • p - Paste after cursor
  • P - Paste before cursor

Other:

  • Enter - Accept/submit line
  • Ctrl+C/Ctrl+D - Exit/abort

Select Mode

  • v or Esc - Exit select mode (clear selection)
  • All motion keys work the same as Normal mode
  • i/a/I/A - Exit select and enter insert mode
  • d/c/y/p - Edit commands work the same

Insert Mode

  • All printable characters - Insert text
  • Esc - Return to normal mode (cursor moves left, vi-style)
  • Backspace - Delete previous character
  • Enter - Accept/submit line
  • Ctrl+C/Ctrl+D - Exit/abort

Motivation

  • Provide Helix-style modal editing for users who prefer that workflow
  • Demonstrate Helix's "selection-first" paradigm is viable in a line editor context
  • Expand Reedline's modal editing options without disrupting existing modes
  • Provide a foundation for future Helix features (multi-cursor, text objects, etc.)

Impact on Codebase

Architectural Integration

The implementation follows Reedline's established patterns:

  1. EditMode trait - Helix implements the same EditMode trait as Vi and Emacs, ensuring consistent integration with the line editor engine
  2. Module structure - Mirrors src/edit_mode/vi/ organization with separate keybindings and mode logic
  3. Keybindings system - Uses existing Keybindings infrastructure for customization
  4. Prompt system - Extends PromptViMode enum with Select variant for proper mode display

Code Organization

src/edit_mode/
├── helix/              # New module (self-contained)
│   ├── mod.rs         # Main implementation (~786 lines)
│   └── helix_keybindings.rs  # Default bindings (~190 lines)
├── vi/                # Unchanged
├── emacs/             # Unchanged
└── mod.rs             # Updated to export helix module

src/prompt/
├── base.rs            # Added PromptViMode::Select variant
└── default.rs         # Added default select mode prompt indicator

src/lib.rs             # Updated to export helix types
examples/helix_mode.rs # New example (~127 lines)
HELIX_MODE.md          # Comprehensive documentation (~181 lines)

No changes to:

  • Core editor (src/core_editor/)
  • Engine (src/engine.rs)
  • Painting/rendering (src/painting/)
  • History, completion, validation, or menu systems
  • Existing Vi or Emacs modes

API Surface

New public exports (non-breaking additions):

  • reedline::Helix - The edit mode struct
  • reedline::default_helix_normal_keybindings() - Default normal mode bindings
  • reedline::default_helix_insert_keybindings() - Default insert mode bindings
  • reedline::PromptViMode::Select - New variant for select mode display

Usage pattern matches existing modes:

// Same pattern as Vi/Emacs
let helix_mode = Box::new(Helix::default());
let editor = Reedline::create().with_edit_mode(helix_mode);

Compatibility Notes

For Application Developers

If you're currently using Reedline:

  • No action required - your code continues to work exactly as before
  • To adopt Helix mode - simply swap your EditMode:
    // Before
    let editor = Reedline::create().with_edit_mode(Box::new(Vi::default()));
    
    // After  
    let editor = Reedline::create().with_edit_mode(Box::new(Helix::default()));
  • If you implement custom Prompt - you may need to add a match arm for PromptViMode::Select

AI Assistance Disclosure

This PR was developed with assistance from OpenCode using Claude Sonnet 4.5. The AI helped with:

  • Initial implementation structure and architecture
  • Iterative refactoring to improve code quality
  • Test coverage and edge case identification
  • Documentation and PR summary writing

All code has been reviewed and validated by the human contributor.

Reduces duplication across 5 mode-switching patterns (i/a/I/A/c)
Eliminates repetitive ReedlineRawEvent::try_from boilerplate across all 26 tests
Reduces duplication in keybinding registration for motion commands
Merges HelixModePrompt and SimplePrompt into single HelixPrompt with simple flag
Adds uppercase word motions that move by whitespace-delimited WORDs:
- W: move to next WORD start (with selection)
- B: move to previous WORD start (with selection)
- E: move to next WORD end (with selection)

Uses existing MoveBigWordRightStart, MoveBigWordLeft, and MoveBigWordRightEnd commands.
Implements character search motions:
- f{char}: find next occurrence (inclusive, extends selection)
- t{char}: till next occurrence (exclusive, extends selection)
- F{char}: find previous occurrence (inclusive, extends selection)
- T{char}: till previous occurrence (exclusive, extends selection)

Uses two-key sequence: press f/t/F/T, then the character to search for.
Cancels pending search if non-character key pressed.

Maps to existing MoveRightUntil/MoveRightBefore/MoveLeftUntil/MoveLeftBefore commands.
- Add PendingCharSearch::to_command() method
- Extract handle_pending_char_search() helper
- Extract start_char_search() helper

Reduces duplication and improves clarity of f/t/F/T implementation.
@fdncred
Copy link
Contributor

fdncred commented Oct 10, 2025

whoa. helix mode sounds cool. thanks. this will be interesting to play around with.

@fdncred
Copy link
Contributor

fdncred commented Oct 11, 2025

the ci needs to be green to proceed. i think these are just formatting cargo fmt --all

@fdncred
Copy link
Contributor

fdncred commented Oct 11, 2025

The manual test sequence is helpful for those who don't use helix. However, on my mac, it doesn't work precisely as you've laid out in those bullets. I'm not sure if they're just wrong or if it's a bug.

@schlich
Copy link
Author

schlich commented Oct 11, 2025

I'll check it out! I've def been testing on windows layout so it's likely something I missed. That documentation there also might have gotten out of sequence in editing too. Good reminder to make sure the keyboard layout is consistent either way

@schlich schlich marked this pull request as draft October 12, 2025 21:59
…lect mode keybindings

This commit implements Helix's selection-first editing model correctly:

Core Changes:
- Split motion bindings into Normal and Select mode variants
- Normal mode: motions collapse selection (reset anchor before movement)
- Select mode: motions extend selection (anchor stays fixed)
- Add separate keybinding function for each mode behavior

Selection Model:
- Add 'v' key to toggle between Normal and Select modes
- Implement ';' to collapse selection to cursor
- Implement Alt+';' to swap cursor and anchor
- Add 'x' to select entire line

New Features:
- Add undo/redo support (u/U keys)
- Add find/till character motions (f/t/F/T)
- Implement proper anchor/cursor/head selection mechanism

Bug Fixes:
- Fix SHIFT+f and SHIFT+t being interpreted as 'F' and 'T'
- Fix duplicate keybinding definitions
- Properly separate Normal and Select mode dispatch

This brings Reedline's Helix mode in line with actual Helix behavior,
where selections are first-class and motions behave differently based
on the current mode.
Add two new interactive examples to help users learn Helix mode:

1. hx_mode_tutorial.rs:
   - Step-by-step guided tutorial
   - Covers all major features with live demonstrations
   - Explains Normal vs Select mode behavior
   - Shows selection model with anchor/cursor
   - Interactive exercises for key features

2. hx_mode_sandbox.rs:
   - Minimal example for experimentation
   - Clean starting point for testing features
   - Shows basic setup and usage

These examples complement the existing test suite by providing
hands-on learning experiences for users new to Helix keybindings.
…model

Add 27 integration tests that verify Helix mode behavior against
the actual Helix editor implementation. Tests were designed based
on research of Helix's codebase using DeepWiki.

Test Coverage:
- Manual workflow sequences (basic editing, mode transitions)
- Motion keybindings (h/l, w/b/e, W/B/E, 0/$, f/t/F/T)
- Selection model tests (Normal vs Select mode behaviors)
- Selection operations (x, ;, Alt+;, v)
- Editing operations (d, c, y, p, P)
- Special behaviors (Esc cursor movement, Ctrl+C/D)
- Complete workflows with multiple edits

Selection Model Tests:
These tests verify Helix's unique anchor/cursor/head mechanism:
- Normal mode: motions collapse selection (move both anchor and head)
- Select mode: motions extend selection (anchor fixed, head moves)
- Proper interaction between modes
- Selection collapse and swap operations

All 27 tests pass, providing confidence that the implementation
matches Helix's actual behavior for single-line editing contexts.

The test file also serves as executable documentation, showing
exactly how each feature should work.

Removed: examples/HELIX_MODE.md (outdated documentation)
@schlich schlich marked this pull request as ready for review October 12, 2025 23:56
@schlich
Copy link
Author

schlich commented Oct 12, 2025

@fdncred if you want to kick off CI again, i was able to fix a few bugs and think it's working pretty smoothly, ran cargo test and cargo fmt --all locally, hopefully it will pass this time, not sure how the spell checker works though

schlich and others added 4 commits October 14, 2025 14:26
Updated the pre-submission checklist to match CI requirements more closely:
- Added --check flag to cargo fmt to verify formatting without modifying files
- Added -D warnings flag to cargo clippy to treat warnings as errors
- Added --all flag to cargo test to run tests across all workspace members
- Added lockfile check command (cargo check --locked)
- Included note about CI testing with multiple feature combinations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Remove the MoveLeft command when transitioning from insert to normal mode
with Esc. This aligns the behavior with standard Helix/Vim conventions
where the cursor position is maintained without moving left.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
schlich and others added 6 commits October 14, 2025 15:26
When exiting insert mode with Esc, the cursor should move left one
position to ensure it's positioned ON a character rather than AFTER
it. This matches standard Helix editor behavior and ensures that
subsequent movements in normal mode properly synchronize the cursor
position with the selection.

The fix ensures that:
- After typing in insert mode and pressing Esc, the cursor moves left
- Normal mode movements (h/l/w/b/etc.) properly select the character
  under the cursor
- The selection range and cursor position remain synchronized through
  mode transitions

Added comprehensive tests to verify cursor/selection synchronization
after insert mode exits and through multiple mode transitions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…nd modes

This commit fixes cursor positioning when exiting insert mode to match
actual Helix editor behavior. The cursor should only move left on Escape
when exiting from append mode (a/A), not insert mode (i/I).

Changes:
- Add restore_cursor field to Helix struct to track entry mode
- Update enter_insert_mode() to accept restore_cursor parameter
- Set restore_cursor=true for append operations (a/A)
- Set restore_cursor=false for insert operations (i/I/c)
- Conditionally move cursor left on Esc only when restore_cursor=true
- Update tests to verify both behaviors (i→Esc and a→Esc)

This matches the behavior verified via DeepWiki against the main Helix
repository where doc.restore_cursor controls this conditional movement.

All 42 Helix tests pass, full test suite (773 tests) passes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…justment

Replace boolean flag with Option<EditCommand> to store the actual exit
behavior rather than a proxy state. This is more idiomatic Rust:

- Stores exactly what needs to happen (the command) vs. why it happens
- Self-documenting at call sites: Some(MoveLeft) vs. true
- Eliminates unnecessary state-to-behavior translation
- No dependency injection where behavior is inherent to the transition

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants