Harden editor rendering, plugins, LSP, and undo#68
Merged
Conversation
Remove the duplicate test-only action reducer so integration tests execute the same editor dispatch path as production. Tighten cursor, line-boundary, and Unicode handling around grapheme-aware editing so movement, deletion, and diagnostics avoid byte-index assumptions.
Route window switching and layout mutations through editor helpers so active-window state is saved and reloaded consistently. Add coverage that verifies split window navigation preserves each window cursor independently.
Extract cursor terminal-position calculation so it can be tested without terminal output. Use the active window buffer line directly when computing display columns, avoiding double application of `vtop` after scrolling.
Use the same terminal cursor position for overlay avoid-cursor placement that rendering uses for the actual cursor. Mark overlays dirty when recalculated placement changes so cursor-driven overlays redraw after moving across screen regions.
Treat render-buffer text writes as terminal cell writes so wide characters reserve their continuation cell. Tighten boundary checks to ignore `x == width` and `y == height` writes instead of allowing them to spill into later cells.
Measure plugin overlay width and right alignment in terminal display columns so wide characters align correctly. Clear the full overlay area before rendering each row to prevent stale cells when content shrinks or shifts.
Use character-aware deletion for command and search backspace instead of byte slicing. Clear the command line before rendering prompt text so Unicode search input and narrow terminal widths do not leave stale cells or underflow padding.
Use display-width-aware, saturating layout math for statusline segments so narrow terminals do not underflow while computing file and position areas. Clear the row before writing statusline segments to avoid stale cells when segments shrink or overlap.
Measure completion labels, icons, details, and documentation with terminal display widths so popup rows stay aligned for emoji and CJK text. Add shared helpers for fitting strings to display columns without splitting grapheme clusters during truncation.
Keep picker lists safe when filtering leaves no matches, and make list row truncation use terminal display widths for wide Unicode labels. Use character-aware backspace and display-width cursor math for picker search text so non-ASCII search terms do not panic or misplace the cursor.
Draw info tooltip text from the dialog content origin instead of applying the horizontal offset twice, which misplaced content away from its border. Measure and pad tooltip rows by display width so wide Unicode text is clipped and filled consistently with other popup surfaces.
Use display-width-aware title measurement and truncation when drawing dialog borders, so long titles cannot underflow the centering calculation. This also keeps Unicode titles aligned with the rest of the popup rendering surfaces that measure terminal columns instead of bytes.
Use terminal display width instead of byte length when reporting the cursor position for command and search prompts. This keeps the cursor aligned with rendered commandline text when prompts contain wide Unicode characters such as emoji or CJK text.
Use saturating geometry for picker dialog, list, separator, and cursor rows so small terminal heights cannot underflow popup layout calculations. Make editor viewport height saturating as well, matching the window manager's reserved status and command line handling on very small screens.
Move info tooltip placement into a small geometry helper that uses saturating math for cursor offsets, horizontal fitting, and available height checks. This prevents hover/info popup layout from underflowing on tiny terminal heights after viewport height is clamped to zero.
Skip statusline and commandline drawing when terminal dimensions leave no valid row for those chrome elements, and use saturating command/search cursor rows. This prevents refresh rendering from underflowing on one-row or zero-size test terminal layouts.
Keep split membership checks from mutating the traversal index before recursing into nested split trees, so resize commands can reach the active window's inner parent split. Also report a no-op when resizing a single window, since there is no parent split ratio to adjust.
Advance the window traversal index when removing the target leaf so later siblings are not mistaken for the same active window during close. This lets closing the first window in a split collapse to the remaining sibling instead of failing to remove the active window.
Build diagnostic rows with display-width-aware fitting so indicators and messages cannot spill beyond the available window columns. Preserve truncation with an ellipsis for wide Unicode diagnostics and keep cramped rows bounded when many diagnostics share a line.
Add a bounded completion popup layout path that clamps popup width, flips above the cursor when there is not enough space below, and limits rendered rows to the available terminal height. Use terminal cursor coordinates for LSP completion popups so placement includes gutter and window offsets instead of buffer-local cursor positions.
Add configurable language server definitions and route editor LSP requests through a manager keyed by document language and workspace root. Keep Rust enabled by default while moving `rust-analyzer` details into server config so other stdio LSPs can be added through `config.toml`.
Add a plugin-owned side panel surface with focus routing, layout reservation, filesystem listing, and directory watch APIs so plugins can build persistent tree-style UI. Register a sample `NeoTree` plugin on `Ctrl-e`, including toggle behavior, file opening, and explicit editor focus handoff after file activation.
Clamp viewport and cursor state after movement actions so commands that overshoot the buffer cannot land past the final navigable line. Cover `G`, oversized line jumps, direct cursor positioning, paging, and scrolling against files that end with a trailing newline.
Keep `w` and `b` from scrolling when their destination line is already visible. Word movement should update the cursor within the current viewport and only scroll when the target word is offscreen. Add a movement regression that asserts both next-word and previous-word motions preserve `vtop` for visible targets.
Allow page movement to use partial pages at file edges so `Ctrl-b` and `Ctrl-f` land on the top or final navigable line instead of refusing to move when a full page is unavailable. Fix visual selection rendering to include the final visible character on selected lines and resolve selection line lengths against buffer lines.
Move `$` to the final visible character instead of one cell past the end of the line, while keeping append-at-end behavior by moving right before entering insert mode for `A`. Bind `$` and `End` in visual modes so visual selections can extend to the line end, and update movement and append tests for the new cursor behavior.
When leaving insert mode, move a one-past-end cursor back onto the final visible character so normal mode does not remain past the line end. Keep insert mode free to use one-past-end positions while editing and add a regression for escaping from an append-at-end cursor.
Add buffer-local transaction undo and redo backed by text edit ranges. Route editor and plugin mutations through the transaction path so insert sessions, deletes, visual edits, paste, indent, and redo share one model. Track clean revisions in the undo history so the dirty indicator clears when undo returns a buffer to its saved state and updates after save.
Derive the default panel side and use key-based reverse sorting for picker matches to satisfy the current Clippy toolchain. Update `bytes` in `Cargo.lock` to the patched release required by the security audit.
Use each window's buffer to calculate gutter width during window-local rendering and mouse coordinate translation. This prevents line numbers in inactive splits from shifting when focus moves between buffers with different line counts. Add a regression test that renders a split with a one-line active buffer and a ten-line inactive buffer, then verifies line 10 is already padded.
Handle `Ctrl-j` and `Ctrl-k` inside the shared picker component so file finder and plugin pickers can move selection without leaving the filter input. Add picker-level tests for control navigation and preserve plain `j` as normal filter text.
Use the buffer navigable line count for cursor bounds, gutter rendering, and viewport rendering so Ropey synthetic trailing lines are not exposed. Preserve unterminated final lines by filling only the remaining row after rendering their content.
Keep an opened trailing line navigable while insert mode is active so `o` at EOF keeps the cursor on the new line. Convert visual selections from grapheme positions to Rope character positions before deleting text.
Join lines when backspace is pressed at column zero on a later line, preserving the expected insert-mode editing behavior. Convert tab insertion from grapheme cursor coordinates to Rope character coordinates before editing multi-codepoint graphemes.
Convert word command cursor positions between grapheme and Rope character offsets before using buffer word helpers. This keeps ZWJ and combining graphemes intact during word motion and deletion. Store full grapheme text in render cells and print that text during diff rendering so UI strings preserve multi-codepoint grapheme clusters.
Clamp panel scroll when row updates shrink the visible model so panel rendering cannot skip past the remaining rows. Keep direct line-open insert actions in the active insert transaction so typing after `InsertLineBelowCursor` or `InsertLineAtCursor` undoes as one insert session.
Reserve both left and right plugin panel widths before laying out editor windows so right-side panels do not draw over editor content. Commit active insert transactions before save checkpoints are marked clean, then reopen insert transactions so continued insert-mode edits keep normal grouping.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Red has several editor paths where terminal geometry, Unicode display width, split-window state, and multi-buffer editing all interact. Those are the exact cases that make a modal editor feel unstable: UI chrome shifts when focus changes, wide characters break alignment, small terminals can underflow layout math, and edits in one buffer can leak into another buffer's undo state. This PR hardens those surfaces while also making the editor easier to extend through configurable LSP servers and persistent plugin panels.
The net change makes rendering and cursor math display-width aware, makes window-local rendering depend on each window's own buffer state, adds a NeoTree-style file panel through new plugin panel APIs, modularizes LSP server resolution, and replaces the old undo stack with buffer-local edit transactions plus redo support.
What Changed
rust-analyzersupplied as a default instead of a hard-coded singleton.Ctrl-e.$in normal and visual modes, and insert-mode escape at line end.Ctrl-rredo, and tracks dirty revision checkpoints so undo can clear the modified indicator when a buffer returns to its saved revision.How to Test
Ctrl-eto toggle the NeoTree panel, move selection in the panel, activate a file or directory, pressEsc, and confirm focus returns to the editor.uandCtrl-r, then confirm each buffer keeps independent history and the modified indicator clears when undo returns to the saved revision.Targeted tests:
cargo test test_inactive_window_uses_its_own_gutter_widthcargo fmt --checkcargo test