vi mode #22
No reviewers
Labels
No labels
bug
dependencies
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
rust
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
navicore/patch-rexx!22
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "line-editor"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Review: PR #22 "vi mode"
This PR replaces
rustylinewithvim-line+crosstermfor vi-mode REPL editing. I've identified several issues that need to be addressed:Critical Issues
1. Terminal not restored on parser/evaluator errors — src/repl.rs:158-179
In the
submit()function, raw mode is disabled before running the REXX code, but ifrun_line()returns an error, the function re-enables raw mode and continues the REPL. However, ifenable_raw_mode()at line 179 fails (e.g., if the terminal is no longer available), it will panic withexpect("failed to re-enable raw mode").Problem: If the terminal cannot re-enter raw mode (SIGTERM during execution, terminal closed, etc.), the REPL will panic instead of gracefully exiting.
Fix: Change line 179 to handle the error:
2. Panic hook captures
original_hookbut never restores it — src/repl.rs:199-205The panic hook is installed but never removed. When the REPL exits normally (Ctrl-D, EXIT command, Action::Cancel), the custom panic hook remains installed for the entire process lifetime.
Problem: If a panic occurs after the REPL exits (unlikely but possible in destructors, atexit handlers, or if
repl::run()is called multiple times), the panic hook will attempt to calldisable_raw_mode()when raw mode is not active, which is harmless but incorrect.Impact: Low severity but violates cleanup expectations.
Fix: Restore the original hook before returning:
3. History navigation doesn't preserve cursor position correctly — src/repl.rs:239-256
When navigating history with
j/kin normal mode, the code callseditor.set_cursor(input.len(), &input)(lines 242, 250) to position the cursor at the end of the loaded history entry.Problem: The second parameter to
set_cursoris the buffer content, but at this pointinputhas already been replaced with the history entry. This is correct. However, the prompt used inrender_lineis hardcoded to"rexx: "(lines 244, 252), which is the normal-mode prompt. This is inconsistent with the actual prompt calculation at lines 295-299.Impact: Low severity but creates visual inconsistency — the prompt will always show normal mode (
rexx:) even if the user is in insert mode when they pressj/k(though vim-line likely ensures this can't happen).Recommendation: Extract prompt selection to a helper function to maintain consistency.
4. Ctrl-D doesn't save history — src/repl.rs:219-224
Ctrl-D breaks out of the event loop immediately without calling
history.save(). History is only saved at line 305, after the loop.Problem: If the user exits with Ctrl-D, history is saved. If they exit via EXIT command or Action::Cancel, history is saved. This is actually correct — Ctrl-D does reach line 305. False alarm on my part; the code is correct.
Safety and Robustness
5.
event::read()error handling is ignored — src/repl.rs:214The loop uses
while let Ok(ev) = event::read(). Ifevent::read()returns an error, the loop exits, raw mode is disabled, and history is saved. This is correct.No issue here.
6. Cursor position calculation assumes valid UTF-8 — src/repl.rs:141
Line 141:
input[..cursor.min(input.len())].chars().count()Problem: If
cursor(a byte offset) is not on a UTF-8 character boundary, slicinginput[..cursor]will panic with "byte index X is not a char boundary".Impact: If
vim-lineever returns an invalid byte offset (e.g., due to multibyte character handling bugs), the REPL will crash.Fix: Add validation:
Or use
input.char_indices()to iterate safely.REXX Semantic Correctness
7. No issues with REXX semantics in this PR
The REPL correctly:
run_line()exit(line 166)The stdin handling in
main.rs:68-89is also correct — it reads piped stdin as a script, which matches standard REXX behavior (e.g., Regina REXX).Summary
Must fix before merge:
enable_raw_mode()error instead of panickingShould fix:
3. Restore original panic hook before returning (lines 303)
Minor:
4. Extract prompt selection to helper function for consistency
No issues:
Let me know if you'd like me to prepare a patch for issues #1 and #6.
PR Review: vi mode (REXX Interpreter)
Reviewed the REPL replacement from
rustylinetovim-line+crossterm. Found 3 critical safety issues that can cause panics and terminal corruption, plus several medium-priority bugs.🚨 CRITICAL ISSUES (Must Fix)
1. Panic on Terminal Mode Setup - src/repl.rs:207, 179
Severity: HIGH
Problem: Panics if:
Impact: Application crashes instead of graceful error. This is problematic since
main.rs:68checksis_terminal()but REPL still callsenable_raw_mode()unconditionally.Fix:
2. UTF-8 Byte Boundary Panic - src/repl.rs:141
Severity: HIGH
Problem:
cursoris a byte offset from vim-line, but code uses it as character index for slicing. Panics ifcursorfalls mid-character in multi-byte UTF-8.Example that crashes:
Impact:
Fix: Validate character boundary or convert to char index before slicing.
3. Terminal State Not Restored on Early Exit - src/repl.rs:223, 294
Severity: HIGH
Problem: Early loop exits skip cleanup at line 309:
Impact: Terminal left in raw mode:
resetorstty saneFix: Use a
Dropguard:⚠️ HIGH-PRIORITY ISSUES
4. History File Race Condition - src/repl.rs:47-55
Severity: MEDIUM
Problem: Concurrent REPL instances cause data loss:
Fix: Use file locking or append-only writes with deduplication on load.
5. Silent History Save Failures - src/repl.rs:49-50
Severity: MEDIUM
Problem: Users have no indication history isn't persisted.
Fix:
6. Missing Ctrl-C Handling - src/repl.rs:219-224
Severity: MEDIUM
Problem: Only Ctrl-D is handled. In raw mode, Ctrl-C doesn't generate signals. Expected behavior (clear line or exit) is missing.
Fix:
ℹ️ LOWER-PRIORITY ISSUES
7. Panic Hook Never Restored - src/repl.rs:199-205
Severity: LOW - Global state pollution
Custom panic hook persists after REPL exit, affecting entire process lifetime. Consider restoring original hook.
✅ VERIFIED CORRECT
run_line(), preserves environment statemain.rs:68-89correctly detects and handles non-terminal inputprev()/next()logic is sound📋 SUMMARY
enable_raw_mode()🎯 RECOMMENDATIONS
Must fix before merge:
.expect()with proper error handlingDropguard for terminal cleanupShould fix:
5. File locking for history
6. Log history errors to stderr
The vi-mode implementation is well-structured with good test coverage, but the 3 critical panics must be addressed to ensure safe operation.
Review of PR #22: vi mode
This PR replaces
rustylinewithvim-line+crosstermto add vi-mode editing to the REPL. The implementation is generally solid with good test coverage. However, I identified several issues:Critical Issues
1. Panic hook not properly restored (src/repl.rs:212-218, 322)
The panic hook captures
original_hookby move, but line 322 callsdrop(std::panic::take_hook())which doesn't restore it — it just installs the default hook. The original hook is lost.Fix:
However, this won't work because
original_hookis moved into the closure. You need to restructure this:Or use a scope guard pattern instead of a panic hook.
2. Panic hook not removed on early return (src/repl.rs:220-223)
If
enable_raw_mode()fails at line 220, the function returns without removing the panic hook. The hook remains installed globally even after REPL exit.Fix: Remove the hook before returning:
3. Unsafe cursor position handling with multi-byte characters (src/repl.rs:259, 267, 296, 302)
Lines 259, 267, 296, 302 call
editor.set_cursor(input.len(), &input), passing the byte length as a cursor position. Ifvim-line'sset_cursor()expects a character offset (not byte offset), this will crash or misbehave on multi-byte UTF-8 input like emoji or Chinese characters.Verification needed: Check whether
VimLineEditor::set_cursor()expects bytes or chars. If chars, use:Similarly, verify that
editor.cursor()at line 142 returns bytes, not chars. If it returns chars, the conversion logic at lines 142-147 is incorrect.Medium Issues
4. Fragile string comparisons for editor mode (src/repl.rs:245, 254, 312)
The code uses string equality checks like
editor.status() == "INSERT". Ifvim-linechanges the string format (e.g., to "Insert"), this breaks silently.Suggestion: If
vim-lineprovides constants or an enum for mode, use those. Otherwise, add a comment noting this is fragile and depends on vim-line's string format.5. History test has incorrect expectations (src/repl.rs:343-344)
After the first
prev(),h.positionisSome, so the second call shouldn't save"current"again — it should use the already-saved value. The test passes because the implementation matches the test, but the semantics are questionable.In practice this doesn't matter because the REPL doesn't call
prev()multiple times with different inputs, but the test is misleading. Consider:Minor Issues
6. History file race condition (src/repl.rs:47-56)
save()usesfs::File::create()which truncates. If two REPL instances run simultaneously, they may overwrite each other's history.Not critical for a REPL, but consider using atomic write-rename if this becomes an issue.
7. Missing welcome message (src/main.rs:138-141)
The old REPL printed a welcome message. The new one doesn't. This is a UX regression, not a bug, but worth noting.
Summary
The implementation is well-tested and handles most edge cases correctly. The main concerns are panic hook management and potential Unicode handling bugs.
Code Review: Vi Mode REPL Implementation
I've reviewed PR #22 which replaces
rustylinewithvim-line+crosstermfor vi mode support in the REPL. The implementation is solid overall, but I found several issues that need attention:Critical Issues
1. Terminal state not restored on panic during key handling (src/repl.rs:217-224)
The panic hook is installed to restore the terminal, but if a panic occurs during the main event loop (lines 237-325), raw mode may not be disabled properly. Specifically:
disable_raw_mode()(line 220)event::read()or key handling (lines 237-324)Impact: Terminal could be left in raw mode if event processing panics.
Recommendation: Wrap the entire event loop in a catch_unwind or ensure cleanup via a Drop guard:
2. Potential infinite loop on
event::read()errors (src/repl.rs:237)The main loop is:
If
event::read()returnsErr, the loop exits silently without restoring the terminal or saving history. This can happen due to:Impact: History not saved on error; terminal state potentially corrupted.
Recommendation: Handle errors explicitly:
3. History navigation doesn't reset position on direct edits (src/repl.rs:259-281)
When navigating history with j/k, the code updates
inputandeditor.set_cursor(), but if the user then makes an edit via vim-line (lines 284-290), the history position is NOT reset. This means:k→ loads "old command" from historyithen types → modifies the lineEscthenkagain → might jump to wrong history entryImpact: Confusing history navigation after editing a recalled command.
Recommendation: Reset history position after applying edits from vim-line:
Safety Issues
4. Unchecked char boundary assumption in render_line (src/repl.rs:142-146)
The code attempts to handle invalid UTF-8 byte positions:
However, this assumes
inputis valid UTF-8. Ifinputcontains invalid UTF-8 (which shouldn't happen with crossterm, but could occur if vim-line has a bug), this could still panic.Impact: Potential panic on malformed input (low probability but possible).
Recommendation: Since
input: Stringis guaranteed to be valid UTF-8 in Rust, this is already safe. However, the comment on line 141 is misleading—it says "avoid panicking on multi-byte input" but the real issue is cursor position validation, not UTF-8 validity. Consider clarifying:Correctness Issues
5. History deduplication not implemented (src/repl.rs:58-60)
The
History::addmethod always appends, even if the command is identical to the most recent entry. This leads to:Creating three identical history entries.
Impact: Cluttered history file; poor UX.
Recommendation: Skip duplicates:
6. Empty lines added to history (src/repl.rs:167-171)
The code adds trimmed input to history even when empty:
Wait, I misread—this is actually correct. Empty lines are NOT added to history (line 171 only executes when
trimmedis non-empty). No issue here.7. Key::char constructor may not exist (src/repl.rs:194, 233)
The code uses:
But I cannot verify from the diff whether
vim_line::Keyhas acharconstructor. If this method doesn't exist, the code won't compile.Status: The code compiles (per your test output), so this is NOT an issue. Just noting for completeness.
Performance Issues
8. Quadratic string allocation in history navigation (src/repl.rs:264-265, 272-273)
On each j/k press in normal mode:
For large history entries (e.g., multi-line REXX programs), this allocates on every navigation step.
Impact: Minor performance degradation when navigating history with long entries.
Recommendation: This is acceptable for a REPL. No change needed unless profiling shows issues.
9. History file rewritten on every exit (src/repl.rs:47-56)
History::savetruncates and rewrites the entire file:For 1000 entries, this is ~50KB of writes per session.
Impact: Minor I/O overhead; potential data loss if process is killed during write.
Recommendation: This is standard for REPL history. No change needed.
Test Coverage
10. No tests for main REPL loop logic
The tests cover:
Historynavigation (lines 343-418)convert_keymapping (lines 421-467)But there are NO tests for:
submitfunction behaviorrender_lineedge cases (cursor at end, empty input, wide characters)Impact: Critical paths untested; regressions possible.
Recommendation: Add integration tests for submit logic and key intercepts. At minimum:
Minor Issues
11. Inconsistent prompt symbols (src/repl.rs:268, 276, 319-322)
The code uses:
"rexx> "for INSERT mode"rexx: "for NORMAL modeBut history navigation (lines 268, 276) hardcodes
"rexx: "even though the mode might be INSERT.Impact: Prompt shows NORMAL indicator during history navigation even in INSERT mode.
Recommendation: Use mode-aware prompt consistently:
12. Unicode em-dash in banner (src/repl.rs:212)
This uses U+2014 (EM DASH) which may not render on all terminals.
Impact: Cosmetic; some terminals show
?or garbled output.Recommendation: Use ASCII dash for maximum compatibility:
Summary
Must Fix (Critical):
event::read()error handling (#2)Should Fix (Correctness):
5. History deduplication (#5)
11. Consistent prompt during history nav (#11)
Nice to Have:
10. Test coverage for REPL loop (#10)
12. ASCII dash in banner (#12)
Not Issues:
4, 6, 7, 8, 9 (explained above)
Overall, this is a well-structured implementation with good test coverage for the core components. The critical issues are around error handling and terminal state management—standard pitfalls when working with raw terminal mode. Once fixed, this will be a solid vi-mode REPL.
Let me know if you'd like me to prepare a follow-up PR with fixes for issues #1-3 and #5.