Macros and profiles: a bindable command system - #2
Conversation
ze2 had a bracket sequence, "[undo] [save]", that ran once and then vanished. PE2/PE3 editors treat a key binding as a stored, named command sequence; this brings the first slice of that model to the command layer so a sequence can be saved and reused instead of retyped. Add "define <name> = [cmd] [cmd]..." to store a sequence and "macro <name>", also usable as "[macro foo]" inside a sequence, to run it. A bare name that is not a built-in runs a saved macro, and built-ins win any collision. An empty body removes a macro, matching PE unbind. Macros may call macros, so the runner clones the sequence out to drop the borrow before each step, and a depth cap with an abort flag stops a self-referential macro from looping and from mutating the document once per unwound frame after the cap trips. Macros live in State, in memory only for now; a profile loader will persist them later. Mirror the new module into the wasm crate, and add docs/ze2.pro as a sample profile.
Milestone one added named macros you had to type. PE2/PE3 editors also let a physical key run a stored command sequence; this adds that half of the model. Add "bind <key> = [cmd] [cmd]...", storing the sequence against an InputKey in State. Key names use the PE modifier prefixes c-, a-, and s- over a letter, digit, named key, or f1-f12; an empty body unbinds. A binding is consulted before the editor, in the shared input-routing path, so it can override the editor's own handling of the key and the built-in shortcuts, the way a PE profile redefines keys. Text-entry contexts such as the command bar, search, and dialogs are excluded so a binding never swallows typing. Give InputKey a Hash impl to key the binding map, expose from_ascii for the key-name parser, and route the command bar, key bindings, and top-level macros through one execute_command_sequence that honors the macro abort flag. Extend docs/ze2.pro with bind examples.
PE editors write a repeated action as a count, so "[3 undo]" runs undo three times. This adds that to the bracket sequence parser. A step may lead with an integer count, expanded to N copies of the invocation at parse time. Expansion keeps the count orthogonal to each command's own argument and lets the abort-on-failure check stop the remaining copies for free, with no change to CommandArgs or the runner. A bare number stays the Goto shorthand, and a count over the 1000 cap rejects the whole sequence rather than silently truncating, so a destructive step cannot half-run. Add a gap example to docs/ze2.pro.
PE editors let you type a command sequence into the buffer and run it in place. This adds that: "execute" reads the selection, or the current line when nothing is selected, and runs it as a bracket sequence. The read is non-destructive and routes through the shared recursion cap, because a line containing "[execute]" would otherwise re-read and recurse without bound. run_macro and execute share one capped runner that also resets the abort flag on a fresh top-level run. A line that is not a valid command sequence, or not valid UTF-8, is reported without running anything. Bind it to a key in docs/ze2.pro.
Normalize the comment style in the macro-system files that carry no other change this round: quote identifiers with double quotes instead of backticks, and drop the em dash. Comments only; no code changes.
The macro commands so far only ran what you typed. PE3 pointed an env var at a profile of commands to run at startup; this adds the same: a "source" command plus startup loading. "source <path>" reads a file and runs each line as if typed in the command bar. Blank and "#" comment lines are dropped and a trailing "\" continues a line. A line that fails to parse or aborts is counted but does not stop the rest; if any line failed, the summary sets the abort flag so an enclosing "source" step stops too. Profile lines run as nested execution, which also bounds a profile that sources itself. At startup the native binary sources the file named by ZE2_PROFILE, on the first frame because handlers need a Context that does not exist before the draw loop. File loading is native only; on wasm the command reports that it is unsupported.
The macro commands could be defined and bound but not captured live. This records the commands you run and plays them back, the classic PE record and replay. "record" toggles capture. While on, execute_command_invocation, the one funnel every command passes through, appends each top-level invocation to a buffer, and "replay" runs that buffer back through the same funnel. A replaying guard keeps a replay out of the recorder, and only depth-zero invocations are captured, so a recorded macro or source call is stored once and re-expands on replay instead of running its inner steps twice. The ceiling is honest: ordinary typing and cursor motion never become commands, so only command-bar, menu, shortcut, and binding actions are captured, with text via the "insert" command. The buffer is capped.
The sample profile listed invented key bindings, one of which (Alt-U) collides with the built-in Unmark shortcut. Rewrite the binding section to mirror the real shortcut table, and move the demo bindings onto keys that are free by default. Add a test that reads the shipped profile and checks every bind key and every define or bind body against the live command table and key parser, so the sample cannot drift from the code.
Associate the .pro extension with the properties syntax so ze2.pro and user profiles get highlighting when opened as documents.
📝 WalkthroughWalkthroughThis PR adds profile-driven key bindings and macros, new editor movement and fill-mark commands, sequence-based command execution, and default-profile loading. It also updates file association handling, shortcut consumption, docs, tests, and web-side ABI metadata. ChangesMacro, profile, and command execution
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Main as main.rs / draw()
participant Loader as load_default_profile / source_profile_file
participant Parser as parse_profile_commands
participant Runner as run_profile_text
participant Exec as execute_command_sequence
Main->>Loader: first frame / ZE2_PROFILE
Loader->>Parser: preflight parse profile lines
Parser-->>Loader: parsed sequences or failure summary
Loader->>Runner: run_profile_text
Runner->>Exec: execute each command sequence
Exec-->>Runner: macro_abort on failure
sequenceDiagram
participant User
participant UI as draw_commandbar / input routing
participant State
participant Exec as execute_command_sequence
User->>UI: key press or command bar input
UI->>State: lookup user_bindings / key_bindings / macros
alt bound sequence found
UI->>Exec: run bound sequence
else no binding
UI->>UI: fallback to command parsing / shortcut invocation
end
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive macro and profile system to the ze2 editor, allowing users to define reusable command sequences, bind keys to sequences, and load custom profiles via the ZE2_PROFILE environment variable. It adds a wide range of editor-action commands for cursor movement, selection, and deletion, alongside support for command recording and replaying. Additionally, the fill_mark command has been enhanced to support multibyte/CJK characters and interactive prompting, and user bindings are now evaluated before the editor or menubar. I have no feedback to provide on these changes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/ze2/src/buffer/mod.rs (1)
3008-3048: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winPreserve the far anchor when updating
Charmarks after fill. BackwardCharmarks are possible becausemark_selection()copiesselection.beg/selection.endas-is, and this always rewritesendtoself.cursor.logical_pos. For a backward mark, that collapses both anchors to the same position and makes laterfill_mark/change_ascii_casecalls no-op. Update whichever side is the normalized max instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/ze2/src/buffer/mod.rs` around lines 3008 - 3048, The Char-mark update in fill_mark currently always rewrites mark.end to self.cursor.logical_pos, which breaks backward Char marks created by mark_selection() because it collapses both anchors. Update the Char branch in fill_mark to preserve the far anchor by normalizing the mark bounds first and only moving the anchor that corresponds to the cursor-facing end, so later fill_mark and change_ascii_case calls still operate on the full selected range.
🧹 Nitpick comments (2)
crates/ze2/src/bin/ze2/draw_editor.rs (1)
478-507: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueFill-mark input allows more than the one character that's actually used.
The editline permits up to 24 columns of input (Line 485), but
TextBuffer::fill_markonly ever consumes the first character (per the comment on Line 489-490) — any extra characters typed are silently dropped. Consider clamping the field to a single character, or clarifying in the prompt text that only the first character is used, to avoid confusing users who type a multi-character fill pattern expecting it to repeat verbatim.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/ze2/src/bin/ze2/draw_editor.rs` around lines 478 - 507, The fill-mark prompt in draw_fill_mark_menu allows more input than TextBuffer::fill_mark actually uses, since only the first character is consumed when RETURN is pressed. Update the UI behavior around ctx.editline/state.fill_mark_input so the field is restricted to a single character, or adjust the prompt text shown via modal_begin/loc(LocId::FillMarkPrompt) to clearly state that only the first character will be used.crates/ze2/src/buffer/mod.rs (1)
3748-3764: 🎯 Functional Correctness | 🔵 TrivialBlock marks silently no-op in
change_ascii_case's new fallback.
linear_mark_rangereturnsNoneforTextMarkKind::Block(Line 3239), so when there's no selection but an active block mark,change_ascii_casefalls through to the early return without any feedback, even thoughfill_mark/delete_mark/etc. all support block marks. Not a regression (block marks weren't supported here before either), but worth considering for parity with the other mark-aware operations.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/ze2/src/buffer/mod.rs` around lines 3748 - 3764, `change_ascii_case` currently falls back through `selection_range_internal(false)` and `linear_mark_range(mark)`, so an active `TextMarkKind::Block` silently does nothing. Update this method to handle block marks consistently with other mark-aware operations like `fill_mark`, `delete_mark`, and `replace_mark`: either support transforming the block-marked regions explicitly or route the block-mark case through the same mark-processing path instead of returning early.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/ze2/src/bin/ze2/input_routing.rs`:
- Around line 26-58: run_user_binding_before_editor currently intercepts keys
even when the menubar is open, which can block menubar navigation and close
handling. Update the early-return guard in run_user_binding_before_editor to
also bail out when state.menubar_visible is set, alongside the existing command
bar, search, dialog, and clipboard sync checks. Keep the rest of the flow
unchanged so user_binding_for_key and execute_command_sequence only run when the
editor is actually able to accept bindings.
In `@crates/ze2/src/buffer/mod.rs`:
- Around line 3093-3132: Use the buffer’s tab size when measuring filled
segments in filled_linear_mark_text/append_filled_segment, because
MeasurementConfig::new(&segment) is currently using the default tab width
instead of TextBuffer’s configured tab_size. Update the measurement path to use
the same tab-size-aware configuration that TextBuffer uses elsewhere (for
example, by passing tab_size into the helper or building the MeasurementConfig
with it) so segments containing tabs are filled to the correct visual width.
In `@crates/ze2/src/tui.rs`:
- Line 3069: The key handling in the textarea input path currently returns false
for vk::B and vk::F, which removes the Alt+B/Alt+F word-navigation shortcut.
Update the relevant match in the TUI key handler to preserve the emacs-style
Alt+B/Alt+F behavior for word navigation, while keeping the existing
KBMOD_FOR_WORD_NAV Left/Right handling intact.
---
Outside diff comments:
In `@crates/ze2/src/buffer/mod.rs`:
- Around line 3008-3048: The Char-mark update in fill_mark currently always
rewrites mark.end to self.cursor.logical_pos, which breaks backward Char marks
created by mark_selection() because it collapses both anchors. Update the Char
branch in fill_mark to preserve the far anchor by normalizing the mark bounds
first and only moving the anchor that corresponds to the cursor-facing end, so
later fill_mark and change_ascii_case calls still operate on the full selected
range.
---
Nitpick comments:
In `@crates/ze2/src/bin/ze2/draw_editor.rs`:
- Around line 478-507: The fill-mark prompt in draw_fill_mark_menu allows more
input than TextBuffer::fill_mark actually uses, since only the first character
is consumed when RETURN is pressed. Update the UI behavior around
ctx.editline/state.fill_mark_input so the field is restricted to a single
character, or adjust the prompt text shown via
modal_begin/loc(LocId::FillMarkPrompt) to clearly state that only the first
character will be used.
In `@crates/ze2/src/buffer/mod.rs`:
- Around line 3748-3764: `change_ascii_case` currently falls back through
`selection_range_internal(false)` and `linear_mark_range(mark)`, so an active
`TextMarkKind::Block` silently does nothing. Update this method to handle block
marks consistently with other mark-aware operations like `fill_mark`,
`delete_mark`, and `replace_mark`: either support transforming the block-marked
regions explicitly or route the block-mark case through the same mark-processing
path instead of returning early.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: acc14c27-0b9a-411a-9af8-064896ad48f6
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (24)
assets/ze2.procrates/lsh/definitions/properties.lshcrates/ze2-web/Cargo.tomlcrates/ze2-web/src/commands.rscrates/ze2-web/src/lib.rscrates/ze2/src/bin/ze2/commands.rscrates/ze2/src/bin/ze2/commands/definition.rscrates/ze2/src/bin/ze2/commands/editing_commands.rscrates/ze2/src/bin/ze2/commands/macro_commands.rscrates/ze2/src/bin/ze2/commands/parse.rscrates/ze2/src/bin/ze2/commands/shortcuts.rscrates/ze2/src/bin/ze2/commands/utility_commands.rscrates/ze2/src/bin/ze2/documents.rscrates/ze2/src/bin/ze2/draw_commandbar.rscrates/ze2/src/bin/ze2/draw_editor.rscrates/ze2/src/bin/ze2/draw_menubar.rscrates/ze2/src/bin/ze2/input_routing.rscrates/ze2/src/bin/ze2/main.rscrates/ze2/src/bin/ze2/state.rscrates/ze2/src/buffer/mod.rscrates/ze2/src/input.rscrates/ze2/src/tui.rsdocs/macros.mdi18n/ze2.toml
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/macros.md`:
- Around line 21-24: The fenced examples in the docs are missing explicit
language labels, which triggers markdownlint warnings and reduces readability.
Update the affected fenced blocks in the macros document to use the appropriate
fence labels, using sh for shell examples and text or ini for profile/config
snippets. Apply the same labeling consistently across the examples in the
referenced sections so the doc uses explicit languages throughout.
- Around line 3-6: Tighten the opening statement in the macros documentation so
it only describes the command surface, not all interactive editor behavior.
Update the introductory wording near the macros overview to avoid implying that
every interactive action is scriptable, and align it with the later sections
that exclude ordinary typing, cursor motion, and editor-owned keys.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d65c8880-b5b5-4da6-a72c-3363aa96522b
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
assets/ze2.procrates/ze2-web/Cargo.tomlcrates/ze2-web/src/commands.rscrates/ze2-web/src/lib.rscrates/ze2/src/bin/ze2/commands.rscrates/ze2/src/bin/ze2/commands/definition.rscrates/ze2/src/bin/ze2/commands/editing_commands.rscrates/ze2/src/bin/ze2/commands/macro_commands.rscrates/ze2/src/bin/ze2/commands/parse.rscrates/ze2/src/bin/ze2/commands/shortcuts.rscrates/ze2/src/bin/ze2/commands/utility_commands.rscrates/ze2/src/bin/ze2/draw_commandbar.rscrates/ze2/src/bin/ze2/draw_editor.rscrates/ze2/src/bin/ze2/draw_menubar.rscrates/ze2/src/bin/ze2/input_routing.rscrates/ze2/src/bin/ze2/main.rscrates/ze2/src/bin/ze2/state.rscrates/ze2/src/buffer/mod.rscrates/ze2/src/input.rscrates/ze2/src/tui.rsdocs/macros.mdi18n/ze2.toml
✅ Files skipped from review due to trivial changes (1)
- i18n/ze2.toml
🚧 Files skipped from review as they are similar to previous changes (18)
- crates/ze2-web/Cargo.toml
- crates/ze2/src/bin/ze2/commands/utility_commands.rs
- assets/ze2.pro
- crates/ze2/src/bin/ze2/draw_editor.rs
- crates/ze2/src/bin/ze2/input_routing.rs
- crates/ze2/src/tui.rs
- crates/ze2/src/bin/ze2/commands.rs
- crates/ze2/src/input.rs
- crates/ze2/src/bin/ze2/commands/parse.rs
- crates/ze2-web/src/commands.rs
- crates/ze2/src/bin/ze2/commands/macro_commands.rs
- crates/ze2/src/bin/ze2/commands/editing_commands.rs
- crates/ze2/src/bin/ze2/commands/shortcuts.rs
- crates/ze2/src/buffer/mod.rs
- crates/ze2/src/bin/ze2/draw_commandbar.rs
- crates/ze2/src/bin/ze2/main.rs
- crates/ze2-web/src/lib.rs
- crates/ze2/src/bin/ze2/state.rs
Load the compiled-in default profile and the user's ZE2_PROFILE into a two-tier binding table: user_bindings run before the menubar and text area so a profile can override any key, while key_bindings stay a global after-editor fallback, and "[noop]" reserves a key for the editor. Add cursor movement, selection, deletion, and overtype commands and bind them in ze2.pro, with vertical movement keeping a sticky column. Improve fill-mark: a bare "fill-mark" prompts for a character through a dialog, a punctuation fill character is no longer swallowed by command name normalization, a full-width character fills correctly, and a line fill no longer extends the mark onto the next line. Drop the noisy profile-load binding warning and document the macro system in docs/macros.md.
Wire the web build's command dispatch and dialogs to the before-editor bindings and fill-mark prompt, dismiss a stale command-bar status on a keypress the way the native loop does, add # Safety docs to the FFI exports, and declare the debug-latency feature it inherits from the shared status bar.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/ze2/src/bin/ze2/commands/utility_commands.rs (1)
187-224: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUnknown-setting/no-document errors are marked as warnings, not errors.
command_bar_error_is_warningis set unconditionally totrue, but the"unknown setting {name}"and"no active document"branches are actual failures, not informational status (contradicting the intent ofcommand_bar_error_is_warningper its own doc comment instate.rs). This renders a mistyped setting query with the same non-alarming yellow style as a successful lookup.🎨 Proposed fix to distinguish failure branches
fn query_setting(_ctx: &mut Context, state: &mut State, args: CommandArgs) { let Some(name) = args.argument.as_deref().map(str::trim).filter(|s| !s.is_empty()) else { return; }; + let mut is_warning = true; state.command_bar_error = if let Some(doc) = state.documents.active() { let tb = doc.buffer.borrow(); match name.to_ascii_lowercase().as_str() { ... - _ => format!("unknown setting {name}"), + _ => { + is_warning = false; + format!("unknown setting {name}") + } } } else { - "no active document".to_string() + is_warning = false; + "no active document".to_string() }; - state.command_bar_error_is_warning = true; + state.command_bar_error_is_warning = is_warning; state.command_bar_active = true; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/ze2/src/bin/ze2/commands/utility_commands.rs` around lines 187 - 224, The `query_setting` command is marking every result as a warning via `command_bar_error_is_warning`, including the failure cases returned from the `"unknown setting {name}"` and `"no active document"` branches. Update `query_setting` so it distinguishes successful lookups from real failures, and only set `command_bar_error_is_warning` to true for informational results; keep it false for the unknown-setting and no-active-document paths. Use the existing `query_setting` function and `command_bar_error_is_warning` field in `State` to locate the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@crates/ze2/src/bin/ze2/commands/utility_commands.rs`:
- Around line 187-224: The `query_setting` command is marking every result as a
warning via `command_bar_error_is_warning`, including the failure cases returned
from the `"unknown setting {name}"` and `"no active document"` branches. Update
`query_setting` so it distinguishes successful lookups from real failures, and
only set `command_bar_error_is_warning` to true for informational results; keep
it false for the unknown-setting and no-active-document paths. Use the existing
`query_setting` function and `command_bar_error_is_warning` field in `State` to
locate the logic.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6cf31400-9c17-4533-a686-bae155ed984d
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
assets/ze2.procrates/ze2-web/Cargo.tomlcrates/ze2-web/src/commands.rscrates/ze2-web/src/lib.rscrates/ze2/src/bin/ze2/commands.rscrates/ze2/src/bin/ze2/commands/definition.rscrates/ze2/src/bin/ze2/commands/editing_commands.rscrates/ze2/src/bin/ze2/commands/macro_commands.rscrates/ze2/src/bin/ze2/commands/parse.rscrates/ze2/src/bin/ze2/commands/shortcuts.rscrates/ze2/src/bin/ze2/commands/utility_commands.rscrates/ze2/src/bin/ze2/draw_commandbar.rscrates/ze2/src/bin/ze2/draw_editor.rscrates/ze2/src/bin/ze2/draw_menubar.rscrates/ze2/src/bin/ze2/input_routing.rscrates/ze2/src/bin/ze2/main.rscrates/ze2/src/bin/ze2/state.rscrates/ze2/src/buffer/mod.rscrates/ze2/src/input.rscrates/ze2/src/tui.rsdocs/macros.mdi18n/ze2.toml
✅ Files skipped from review due to trivial changes (2)
- i18n/ze2.toml
- docs/macros.md
🚧 Files skipped from review as they are similar to previous changes (17)
- crates/ze2/src/input.rs
- crates/ze2-web/Cargo.toml
- crates/ze2/src/bin/ze2/draw_menubar.rs
- crates/ze2/src/bin/ze2/draw_editor.rs
- crates/ze2/src/tui.rs
- crates/ze2/src/bin/ze2/input_routing.rs
- crates/ze2/src/bin/ze2/main.rs
- crates/ze2/src/bin/ze2/commands/parse.rs
- crates/ze2/src/bin/ze2/draw_commandbar.rs
- crates/ze2/src/bin/ze2/commands/macro_commands.rs
- crates/ze2/src/bin/ze2/commands/shortcuts.rs
- assets/ze2.pro
- crates/ze2/src/bin/ze2/commands/editing_commands.rs
- crates/ze2/src/bin/ze2/state.rs
- crates/ze2/src/bin/ze2/commands/definition.rs
- crates/ze2-web/src/lib.rs
- crates/ze2/src/buffer/mod.rs
Why
The PE2 and PE3 editors were built around a single idea: every editing action is a named command, and the editor is configured by a plain-text profile of those commands. A profile named keys to command sequences, defined reusable macros, and sourced other profiles, so users reshaped the editor without touching the binary. Nothing in the editor was privileged over what you could type at the command line.
ze2 had lost that. Key handling was hard-coded: some keys were global shortcuts, others owned by the text-area widget, and none were reconfigurable. This series brings the PE model back. It makes the same commands you type in the command bar the building blocks for macros and key bindings, loaded from a compiled-in default (
assets/ze2.pro) plus an optional user file (ZE2_PROFILE) that overrides it, so the whole key map is data a user can read, override, or replace.What
[cmd] [cmd]with per-step arguments ([fill-mark -]) and repeat counts ([3 undo]); a bad token rejects the whole sequence.define/bind/source; an empty body removes an entry;[noop]reserves a key for the editor.main's existing behavior is preserved unless a profile overrides it.Commits
Commits 1-8 build the macro/profile core incrementally; 9 associates the
.proextension with the properties syntax; 10 is the large one (movement / selection / deletion / overtype commands, two-tier dispatch, a fill-mark character prompt with punctuation and full-width fills, dropping a noisy load warning, anddocs/macros.md); 11 mirrors it in the wasm web build.Commit 10 is not as minimal as the rest; its concerns are intertwined across shared files, and a cleaner split needs an interactive rebase. Every commit compiles on its own.
Testing
TUI paths (live movement, paging, the fill-mark dialog) were checked by hand; the buffer / parse / profile logic under them is covered above. The wasm target builds via
cargo check.Docs
assets/ze2.pro: the effective default profile and key mapdocs/macros.md: full guide: format, key names, dispatch, examplesSummary by CodeRabbit
ZE2_PROFILE.