Make line-block toggles (lists/blockquote/code fence) undoable (#20)#28
Merged
Conversation
Toggling a numbered/bulleted list, blockquote, or code fence changed both the line's paragraph indent (via updateLine) and a line-anchored rich span (via richSpanManager). Neither went through editManager.recordEdit, so the toggle was invisible to undo/redo and Ctrl+Z reverted the previous edit instead. Introduce a dedicated TextEditOperation.LineBlock variant that snapshots each affected line's content and block-span set both before and after the toggle. Apply/redo restore the after state; undo restores the before state — including any mutually-excluded block demoted as a side effect — in a single history entry. The toggle now routes through editManager.toggleLineBlock; the inner span mutations use the direct richSpanManager path to avoid double-recording, and importMarkdown stays on the non-recording path so document loading does not pollute undo history. Extend every exhaustive when over TextEditOperation: RichSpanManager (passthrough, toggles don't move text) and SpellCheckState (null, no text change). SpellCheckingTextEditor already has an else branch. Fixes #20 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A LineBlock toggle changes no text positions, so applyOperation must keep the active selection like the other span operations do. Add LineBlock to the selection-preserving set so the select-lines -> click-list flow no longer clears the selection. Add tests covering the highest-risk cases, all via the real markdown.toggle* entry points: a character bold span and a standalone highlight rich span survive toggle/undo/redo unchanged, and undoing an ordered-list toggle that demoted an existing bullet restores the bullet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Problem
Toggling a numbered/bulleted list, blockquote, or code fence was not undoable (#20). Pressing Ctrl+Z after toggling a list did not remove the list — it reverted the previous edit.
The toggles in
MarkdownExtension(toggleOrderedList/toggleBulletList/toggleBlockquote/toggleCodeFence→toggleLineBlock) calledapplyLineBlock/demoteLineBlock, which mutated both the line's paragraph indent (viaupdateLine) and a line-anchored rich span (viarichSpanManager). Neither went througheditManager.recordEdit, so the change was invisible to undo/redo. PR #26 made standalone rich spans undoable but deliberately left line-block toggles out; this PR finishes that work.Design: dedicated
TextEditOperation.LineBlockvariantI chose a dedicated variant over a generic
Compositetransaction. The line-block toggle has a fixed, well-defined shape, so a dedicated op lets us capture each affected line's prior/nextAnnotatedStringand block-span set directly and invert cleanly — simpler and less error-prone than threading a list of sub-operations each with their own metadata.transformOffsetis a no-op because toggles change paragraph style only, not text length.Each
LineBlockChangesnapshots, per affected line:contentBefore/contentAfter— exact line content (so undo restores paragraph + text style precisely)blockSpansBefore/blockSpansAfter— the line-anchored block span styles present in each stateCapturing the full before/after span set (not just the toggled style) means a mutually-excluded block demoted as a side effect (e.g. toggling an ordered list onto a bullet line) is also restored correctly in the same atomic step.
MarkdownExtension.toggleLineBlocknow routes througheditManager.toggleLineBlock, which captures before/after, applies the toggle via the direct (non-recording) path, then records exactly oneLineBlockhistory entry. The inner span mutations deliberately use the directrichSpanManagerpath to avoid double-recording.importMarkdownis unchanged and stays on the direct path, so document loading still does not pollute undo history.Exhaustiveness
Every exhaustive
when (operation)overTextEditOperationwas extended:TextEditManager—applyOperation,undo(newapplyLineBlockState/undoLineBlock; redo reusesapplyOperation)RichSpanManager.updateSpans— passthrough (handleSpanOnly), toggles don't move textSpellCheckState.invalidateSpellCheckSpans—null, no text changeSpellCheckingTextEditor.computeAffectedRangesalready had anelsebranch.Verified with the full
./gradlew check(the cross-module build that caught #26's first break).Tests
LineBlockUndoTestsandLineBlockRedoTests, all driving the realmarkdown.toggle*entry points:Verification
./gradlew check→ BUILD SUCCESSFUL (iOS tasks skipped on Windows).Fixes #20