Code Review
Pending code quality improvements and refactoring opportunities.
Note: file/line references are best-effort and can drift as the code changes; treat line numbers as approximate.
Large Functions
- src/app/render.rs:5 — top-level
renderruns ~450 lines covering layout calculation, plugin hook firing, file explorer rendering, and status/prompt UI. The amount of responsibility in one function hurts readability and testing; factoring into helpers (layout, plugin hook prep, explorer rendering, status/prompt rendering) would make regressions easier to spot. - src/input/keybindings.rs:376 —
Action::from_stris a ~260-line string-to-enum match. A data-driven table would reduce boilerplate and avoid missing new actions when added to the enum. - src/input/commands.rs:89 —
get_all_commandsis a ~540-line literal list in one function. This is brittle to maintain (hard to diff/review additions) and couples command metadata to code; consider a data table or config-driven source with tests for completeness. - src/primitives/highlighter.rs:78 —
highlight_configis ~450 lines of repetitive per-language setup; moving to a data table (language -> queries/config) would reduce duplication and make it harder to forget highlight names when adding languages. - src/config.rs:566 —
default_menusis a 420-line literal definition. Consider moving menu data to structured config or a table to make changes easier to diff/test and to avoid bloating code with data. - src/view/viewport.rs:521 —
ensure_visibleruns ~230 lines of layout math and clamping in one method; breaking into smaller helpers (e.g., horizontal/vertical logic, scroll computations) would make correctness checks and future changes safer. - src/input/keybindings.rs:1196 —
format_actionspans ~200 lines mirroringfrom_str; another sign a data-driven action registry would reduce duplication and risk of drift. - src/view/ui/view_pipeline.rs:134 —
nextis a 160+ line iterator step that handles multiple line types and wrapping; decomposing per-case helpers would make it easier to verify wrapping and newline behaviors. - src/model/buffer.rs:575 —
get_text_range_mutis a 160+ line method with intertwined chunk handling, file streaming, and cache management. The dense control flow increases the risk of off-by-one or range bugs; extract helpers per buffer variant (loaded vs unloaded) and centralize bounds checks. - src/services/plugins/runtime.rs:1861/1967/2066/2143/2159/etc. — repeated
pending_responses.lock().unwrap()(and other mutexunwrap()s) in ops will panic if the mutex is poisoned by a plugin panic. These should handle poisoning and return an error to the plugin rather than crashing the runtime. - src/view/ui/file_explorer.rs:148 — renderer calls
view.tree().get_node(node_id).expect("Node should exist"). A desynced tree (e.g., after FS errors or refresh races) will panic the UI; prefer graceful fallback or placeholder row. - src/view/file_tree/tree.rs:138 —
expand_nodeusesself.get_node(id).unwrap()while doing async FS reads. If the node was removed between calls, this panics instead of returning an IO error; return a proper error for missing nodes to avoid crashing the explorer. - src/view/ui/status_bar.rs:149 —
render_statuspulls cursor info with ad-hoc line iteration and cached line numbers in a long method; consider extracting helpers for cursor position and diagnostics summary to reduce the ~200-line render method complexity. - src/model/cursor.rs:220 —
primary()/primary_mut()useexpect("Primary cursor should always exist"). If a bug ever removes the primary cursor (e.g., during multi-cursor deletion), the editor will panic. Prefer returning an error or recreating a primary cursor to keep the UI alive. - src/model/marker_tree.rs:494/509/512/517/etc. — AVL rotations and queries use unchecked
unwrap()on child pointers. Any tree corruption (e.g., from earlier logic bugs) will panic. Consider defensive checks or debug assertions gated for release builds to avoid crashing the editor. - src/view/split.rs:162 —
ensure_layoutreturnsself.layout.as_ref().unwrap(). If callers forget to callensure_layoutbeforeget_layoutusage, rendering will panic. Consider returningOption<&Layout>or asserting via a Result to make misuse harder in production. - src/view/ui/split_rendering.rs:1642/1838 — rendering paths
unwrap()optional highlight colors/positions when overlays and syntax spans overlap. If the lookups ever returnNone(e.g., missing semantic span), the renderer will panic; handleNonewith defaults to keep the UI resilient. - src/view/prompt.rs:974 —
selected_text().unwrap()in selection paths; production prompt operations should avoid panics when no selection exists (e.g., racey UI updates or plugin-driven prompt changes). - src/input/buffer_mode.rs: ModeRegistry and BufferMode rely on string names without validation; missing mode references during inheritance or resolve silently stop traversal, which can mask misconfigurations. Consider explicit errors/logging for unknown parent/mode to aid debugging.
- src/view/viewport.rs:895 —
ensure_cursors_visibleunwraps min/max cursor positions; if called with an empty cursor list (due to upstream logic bugs), it will panic. Guard against empty input to keep scrolling resilient. - src/view/ui/suggestions.rs:80/87 — width calculations use
.max().unwrap_or(0)on suggestion lists; if any suggestion text/keybinding is extremely wide, width computation and rendering alignment are packed into the main render function. Consider extracting layout calculation and handling unreasonably wide strings to avoid overflow/panic in rendering math. - src/services/signal_handler.rs:23/104 — global backtrace storage uses
Mutex::lock().unwrap(); any poisoning (e.g., panic during signal handling) will panic again. Signal handlers should avoid unwraps and degrade gracefully to prevent double panics during shutdown. - src/services/async_bridge.rs:300/324/329/356/... — multiple
unwrap()s on channel send/recv in the bridge; if the channel is closed (e.g., runtime shutdown), the bridge will panic instead of allowing a clean exit. Return errors and let the main loop terminate gracefully. - src/services/process_limits.rs:405/406/459/476/496/503 — heavy use of
unwrap()around serialization and system resource reads; a failure to read system memory or serialize limits will panic instead of bubbling a configuration error. Prefer error propagation. - src/services/plugins/api.rs & event_hooks/process: widespread
RwLock::unwrap()and channelunwrap()s. A poisoned lock or closed channel will panic plugins and potentially the editor. Replace with error paths that surface to the plugin caller or log-and-drop to keep the host stable. - src/input/command_registry.rs — plugin command registration/history uses
RwLock::unwrap()in production paths; a poisoned lock (plugin panic) will crash the editor. Handle lock errors and surface failures to plugins. - src/input/actions.rs:68 — block selection reads
cursor.block_anchor.unwrap()when in block mode; if state enters block mode without an anchor, movement will panic. Guard against None to keep editing stable. - src/services/plugins/process.rs:132/166/195/226 — plugin process bridge uses
recv().unwrap(); closed channels during shutdown will panic the host. Treat channel closure as cancellation to allow clean teardown. - src/services/plugins/process.rs — blocking
recv()loops have no timeout/backoff; a misbehaving plugin can hang the thread. Consider async or timeout-aware handling to avoid host hangs. - src/services/async_bridge.rs — uses unbounded std::sync::mpsc channels with no backpressure or eviction; a burst of async messages (e.g., LSP floods) can grow memory unbounded. Consider bounded channels or dropping strategies plus explicit error propagation on lock/channel failure.
- src/services/plugins/event_hooks.rs —
apply_event_with_hooksappears to be legacy/test-only and is not part of the current editor edit path (which applies edits viaEditor::apply_event_to_active_bufferand queues plugin hooks asynchronously). If this module becomes production-critical again, it should isolate hook execution (catch_unwind/log) and avoid panics on poisoned locks. - src/app/plugin_commands.rs — some plugin text-editing commands (
InsertText,DeleteRange,InsertAtCursor) mutate buffers without going throughEditor::apply_event_to_active_buffer, which can bypass cross-cutting concerns (LSP change notifications, search highlight clearing, cursor/view sync, plugin after-insert/delete hooks). Consider routing “real edit” plugin commands through the centralized apply path, or document them as “raw/unsafe” edits. - src/view/ui/tabs.rs —
render_for_splitpacks tab width calculation, scroll computation, and rendering into one long function; consider splitting into layout calculation and render steps to improve readability and reduce bugs in scroll math. - src/services/process_limits.rs — applies limits via cgroups/setrlimit with many unwraps noted; also lacks clear fallback/reporting when limits can't be applied (e.g., non-Linux or missing cgroup perms). Return actionable errors so callers can decide to continue without limits.
Test Code
- src/view/ui/menu.rs:655/776/811/830 — menu parsing/rendering tests use
unwrap(); production parsing should handle malformed plugin menu contributions gracefully. - src/services/fs/slow.rs — tests use
TempDir::new().unwrap()/read_dir().awaitunwraps; ensure production slow backend surfaces IO errors cleanly. - src/view/popup.rs — selection tests
unwrap()the selected item; production popup handling should guard empty lists.