Unify editor and preview zoom - #518
Draft
leevi2010-cursor wants to merge 9 commits into
Draft
Conversation
Provide Actual Size (Cmd+0), Zoom In (Cmd++), and Zoom Out (Cmd+-) menu items plus a toolbar dropdown so users can rescale the rendered preview for readability, presentations, or larger displays. Zoom level persists across launches and snaps to preset increments: 50, 75, 90, 100, 110, 125, 150, and 200 percent.
Implements transient per-document zoom as requested in schuyler#335: - Cmd+ zooms in (10% increments, max 300%) - Cmd- zooms out (10% decrements, min 50%) - Cmd+0 resets to actual size - Zoom applies to both editor and preview when preference is enabled - Zoom is transient (not saved to preferences) - Menu items validate at zoom limits - Does not mutate base font preference Fixes schuyler#335
- Move kMinZoom/kMaxZoom to file-scope constants - Fix preview zoom when previewZoomRelativeToBaseFontSize is off - Fix floating-point comparison in resetZoom: validation
Adds MPZoomTests.m with 22 tests covering zoom multiplier basics, menu validation, preference observer behavior, and tab stop calculation. Two tests are expected to FAIL against current code as TDD regression guards: - testSetupEditorPreservesZoomedFontSize: guards against setupEditor: resetting the editor font to its unzoomed base size, clobbering any active zoom. - testTabStopsReflectZoomedFontSize: guards against tab stops being computed from the base font instead of the zoomed font. Related to schuyler#335
Bug 1: Cmd+0 conflict. "Actual Size" was bound to Cmd+0, which collides with Format > "Paragraph". Remove the shortcut from "Actual Size" (menu only) and change "Zoom In" from "+" to "=" so Cmd+= works without Shift on US keyboards (standard macOS zoom behavior). Bug 2: Editor font reset on preference changes. setupEditor: applied the raw base font via self.editor.font = font, clobbering any active zoom when the font, style, or line-spacing preference changed. Bug 3: Tab stops computed from base font. setupEditor: computed tab stops from the unzoomed base font's space width, so tabs appeared at the wrong width after zoom. Add a zoomedEditorFont helper that returns base font x zoomMultiplier. setupEditor: now uses it for both the tab stop calculation and the editor font assignment, fixing bugs 2 and 3 in one change. Simplify applyCurrentZoom to delegate to setupEditor: (which already calls scaleWebview), so zoom actions also refresh tab stops and the syntax highlighter's font cache. Related to schuyler#335
XIB: The previous fix moved Cmd+0 off "Actual Size" but introduced two new collisions - Cmd+= with Format > "Highlight" and Cmd+- with Format > "Strikethrough". Change "Zoom In" to Cmd+Plus (keyEquivalent "+") and add Shift+Cmd to "Zoom Out" so both use the Shift modifier and neither collides with the Format menu shortcuts. Performance: applyCurrentZoom previously routed through setupEditor:, which does far more than the zoom path needs - it deactivates and reactivates the PEG Markdown highlighter, re-reads and re-applies the stylesheet from disk, and replaces the editor's CALayer. Extract the font and paragraph-style logic into a dedicated applyEditorFontAndParagraphStyle helper that both setupEditor: and applyCurrentZoom call, so zoom keystrokes skip the highlighter re-parse and CALayer rebuild. Tests: remove outdated "expected to FAIL" comments now that the bugs are fixed, and drop testSetupEditorDoesNotResetZoomMultiplier, which only checked that a CGFloat property stayed unchanged across a method that never touched it - the real Bug 2 regression is covered by testSetupEditorPreservesZoomedFontSize. Related to schuyler#335
Extract previewScale from scaleWebview so the scale computation is unit-testable without mocking WebView. The PR's scaleWebview change removed the early-return when previewZoomRelativeToBaseFontSize is OFF, so add four tests that pin both branches of the calculation: - preference OFF + zoom 1.0 -> 1.0 (no-op equivalence) - preference OFF + non-default zoom -> tracks zoomMultiplier - preference ON + non-default zoom -> (fontSize/14) * zoomMultiplier - preference ON + zoom 1.0 -> matches legacy fontSize/14 ratio Each test saves and restores the preference values it touches. Related to schuyler#335
schuyler
reviewed
Jul 11, 2026
schuyler
left a comment
Owner
There was a problem hiding this comment.
Thanks for the PR, @leevi2010-cursor!
Issues
- Toolbar zoom dropdown does nothing: the popup menu items are assigned
target = self.documentinsidesetupToolbarItems(called from-init), where thedocumentoutlet is still nil, so clicking a preset is a silent no-op — the deferred-dispatch proxy pattern already used elsewhere in this file avoids exactly this. - Format shortcuts lost, not relocated: MainMenu.xib strips the key equivalents from Highlight (⌘=), Strikethrough (⌘−), and Paragraph (⌘0) rather than reassigning them per #470, so those commands lose their shortcuts entirely.
- Two contradictory zoom test suites: MPZoomTests.m asserts flat ±0.1 stepping on a per-document
zoomMultiplier, while MPPreviewZoomTests.m asserts preset-snapping on a shared preference — only the latter matches the implementation, and the stale suite would benefit from reconciliation or removal. - Headline behavior untested: nothing verifies that zoom is actually shared across open document windows (the PR's core claim), and such a test would also have surfaced the nil-target bug above.
Suggestions
resetZoom:/selectDocumentZoom:writedocumentZoomLeveldirectly, bypassing the clamp insetZoomMultiplier:, so routing through the setter would keep bounds enforcement in one place.- The zoom preset array is duplicated between MPToolbarController.m and MPDocument.m with a hand-sync comment, and a single shared source would avoid drift.
- The new
toggleEditorPane:→editorStartInPreviewModechange appears to be PR #519's feature bundled in here, so it's worth confirming that's intentional or splitting it out. - A dealloc round-trip test for the toolbar controller's new KVO observer would guard the add/remove lifecycle.
- help.md has no zoom section and doesn't note the moved Format shortcuts, leaving users no in-app reference for either.
- Several tests early-
returnwhen headless, silently passing with zero assertions, where XCTSkip would make those skips visible in CI.
14 tasks
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
Unifies editor and preview zoom into a single document-level control, as proposed in issue #470.
This builds on the implementations and retained commit history from #442 and #395.
Testing
MPZoomTests,MPPreviewZoomTests, andMPToolbarControllerTests)MainMenu.xibRelated to #470 and #335.