CursorTarget::OnCharacter clamps to last visible char (Normal mode).
CursorTarget::InsertionPoint allows one past last char (Insert mode).
Centralizes clamping in the handler; simplifies configuration layer.
- Move Command enum to commands/mod.rs with NAMES, from_name, takes_path_arg
- Replace manual match in CommandAPI::execute with Command::from_name
- Derive canonical names for completion from Command::NAMES (first per variant)
- Simplify complete() to use Command::takes_path_arg instead of complete_path_arg
- Fix bug: 'h' was incorrectly listed as alias for both help and finder
- Add CharClass enum (Word, Whitespace, Other) with char_class() fn
- Add next_pos/prev_pos helpers for line-boundary-aware buffer traversal
- Implement word_forward (w), word_backward (b), move_to_last_line (G)
- Wire gg via pending_operator mechanism (g then g → top of file)
- Also: :help off now uses BufferAction::Hide instead of Close
Hardware cursor shape (DECSCUSR/SetCursorStyle) proved unreliable
across terminals (e.g. tmux intercepts or ignores the escape).
Switch to a pure software approach: render a '|' span with
cursor_style (REVERSED) at the cursor x position on the cursor
line, the same way the Normal-mode block cursor is rendered.
No terminal feature detection needed.
- GutterDef: ergonomic non-object-safe trait with associated Prepared type
- DynGutterDef: object-safe wrapper using Box<dyn Any + Send> (erased-serde pattern)
- Blanket impl<G: GutterDef> DynGutterDef for G with downcast_ref in decorate_any
- LineCtx (Copy): per-line context with line_in_file: Option<usize> for beyond-EOF rows
- LineDecoration { left, right: Option<StyledText> }: side declared per decoration
- Built-in LineNumbers implements GutterDef directly
- BufferSettings: Vec<GutterColumn> -> Vec<Box<dyn DynGutterDef>>
- Renderer: prepare_boxed once per gutter per frame, decorate_any per line
The single-variant Buffer::Text(TextBuffer) enum added unnecessary
destructuring at every usage site. Since there's only one buffer type,
flatten it to just Buffer directly.
Replace InsertCharacter(char) with InsertText(String) for chars, newlines, and future paste/macros. Add DeleteRange { from, to } for coordinate-based range deletion. Configuration returns Vec<EditorAction> to enable future compound operations. Add pending_operator state for multi-key dd sequence.
- Add BufferProperties struct (listed, ephemeral, read_only, command)
- Add FocusCommandBuffer action so Configuration doesn't need buffer IDs
- Help popup uses info() properties and is dismissible with Escape
Replace PopupHelp and ToggleFinder with a general pane visibility system:
- Add PanePosition enum (Main, Floating, future: Sidebar, Split)
- ShowPane { buffer, position } displays a buffer in specified layout
- HidePane { buffer, delete_buffer } removes from layout, optionally deletes
Commands now compose LoadBuffer + ShowPane for popups. Removed FinderBuffer
and Buffer::Finder variant - all buffers are now TextBuffer.
Also adds jj preference to CLAUDE.md, and does some other refactoring
Replace OpenFile/WriteBuffer with a more expressive LoadBuffer/SaveBuffer
that supports multiple content sources and destinations. This enables
scripts to populate buffers from files, literal text, or empty, and save
to explicit paths or backing files.
- Add BufferSource enum (File, Text, Empty)
- Add BufferDestination enum (File, future: Clipboard, etc.)
- LoadBuffer/SaveBuffer can target specific buffers or active buffer
- Add TextBuffer::from_text() constructor
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move cursor movement logic from EditorCore/Cursor into Configuration layer.
This prepares the codebase for a Turing-complete scripting DSL by keeping
EditorAction minimal (only absolute MoveCursor { x, y }) and having the
scripting layer compute all movement targets via BufferQuery.
- Add BufferQuery trait for read-only buffer access
- Remove CursorMovement enum, replace with MoveCursor { x, y }
- Simplify Cursor to just hold position (remove move_cursor method)
- Configuration now computes movement targets using BufferQuery
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace the dual-rate timer rendering architecture with a direct
render-after-input loop. The old design used a background tokio task
(EventHandler) that ran two interval timers — 30 FPS while "active"
and 2 FPS when idle — forwarding events through an mpsc channel. This
introduced up to ~33ms of latency between a keypress and its screen
update, and added unnecessary complexity (background task, channel,
activity threshold heuristic).
The new design uses a single tokio::select! loop with three arms:
1. Keyboard input: renders immediately after processing, then drains
any additional buffered input via non-blocking poll before drawing
2. Async command results: handles actions from the command channel
(file I/O completion, etc.) and renders
3. Idle timeout (500ms): renders for background updates like cursor
blink
Structural changes:
- Remove Event enum, EventHandler struct, and its background task
- TerminalUI now owns crossterm::event::EventStream and the command
output UnboundedReceiver directly
- EditorCore::new() returns (Self, UnboundedReceiver<EditorAction>)
instead of storing the receiver internally
- Remove EditorCore::process_commands() — its logic moves into the
main loop's select arm for command output
- Make EditorCore::handle_action() pub(crate) so the main loop can
call it directly for command results
- Extract process_key() helper for KeyCode-to-Input mapping
- Add try_poll_crossterm() for non-blocking input draining
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>