Move Line Up/Down
May 18, 2026 · View on GitHub
Overview
Move Line Up/Down (Alt+↑/↓) allows users to reposition the current line by swapping it with the adjacent line above or below. The cursor follows the moved line, maintaining column position.
Key Files
| File | Purpose |
|---|---|
src/app.rs | Key consumption before render, handle_move_line() implementation |
src/state.rs | pending_cursor_restore field for cursor positioning |
src/editor/widget.rs | Applies pending cursor position after TextEdit render |
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Alt+Up | Move current line up (swap with line above) |
Alt+Down | Move current line down (swap with line below) |
Implementation Details
Key Consumption Strategy
The implementation uses a pre-render key consumption pattern to prevent egui's TextEdit from processing the arrow keys:
- Before render:
consume_move_line_keys()usesctx.input_mut()withconsume_key()to intercept Alt+Arrow keys - During render: TextEdit renders without processing the consumed arrow keys
- After render:
handle_move_line()executes with correct cursor position
This pattern is critical because without consuming the keys before render, TextEdit would process the arrow key first (moving the cursor), causing an off-by-one error in line detection.
// Before render - consume keys to prevent TextEdit processing
fn consume_move_line_keys(&mut self, ctx: &egui::Context) -> Option<isize> {
ctx.input_mut(|i| {
if i.consume_key(egui::Modifiers::ALT, egui::Key::ArrowUp) {
return Some(-1);
}
if i.consume_key(egui::Modifiers::ALT, egui::Key::ArrowDown) {
return Some(1);
}
None
})
}
Line Swap Algorithm
The move operation uses a simple swap approach:
- Get current line number from
tab.cursor_position(0-indexed) - Check boundary conditions (can't move first line up, can't move last line down)
- Split content into lines vector
- Swap current line with adjacent line using
Vec::swap() - Join lines back into content string
Cursor Following
To ensure the cursor follows the moved line, the implementation sets tab.pending_cursor_restore:
- Calculate new cursor position (new line start + original column)
- Set
tab.pending_cursor_restore = Some(new_cursor_char)
Note: EditorWidget should apply this char index to FerriteEditor on the next frame; wiring is incomplete as of v0.3.0 — cursor may lag until a future fix. Line/column on Tab is still updated for outline sync.
Behavior
| Scenario | Result |
|---|---|
| Cursor on line 2, Alt+Up | Line 2 swaps with line 1, cursor moves to line 1 |
| Cursor on line 2, Alt+Down | Line 2 swaps with line 3, cursor moves to line 3 |
| Cursor on first line, Alt+Up | No action (boundary) |
| Cursor on last line, Alt+Down | No action (boundary) |
| Cursor at column 5, move line | Cursor stays at column 5 on new line |
Undo/Redo Support
Each move operation is recorded via tab.record_edit(), allowing:
- Single undo to restore line to original position
- Redo to re-apply the move
Related Patterns
This implementation shares patterns with:
- Undo/Redo keys (
consume_undo_redo_keys) - Same pre-render key consumption pattern - Duplicate Line (
handle_duplicate_line) - Similar line manipulation and cursor handling - Go to Line - Uses same
pending_cursor_restoremechanism for cursor positioning
Testing
Manual test cases:
- Move single line up and down multiple times
- Verify boundary behavior at document start/end
- Verify cursor column is preserved after move
- Verify undo restores original position
- Rapid consecutive moves (Alt+Down multiple times)