Skip to content

feat: add pinch-to-zoom support to editor - #2963

Open
gahanad wants to merge 4 commits into
Acode-Foundation:mainfrom
gahanad:feat/editor-pinch-to-zoom
Open

gahanad wants to merge 4 commits into
Acode-Foundation:mainfrom
gahanad:feat/editor-pinch-to-zoom

Conversation

@gahanad

@gahanad gahanad commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds pinch-to-zoom support to the editor to make editing more convenient on mobile devices.

Users can adjust the editor's font size using a two-finger pinch gesture, improving readability and usability on smaller screens.

Changes Made

  • Added a pinch-to-zoom extension for the editor.
  • Dynamically adjusts the editor font size based on the pinch gesture.
  • Enforces minimum and maximum font-size limits.
  • Saves the updated font size setting when the gesture ends.
  • Cleans up touch event listeners when the extension is destroyed.
  • Added unit tests covering pinch-zoom behavior and font-size calculations.

Testing

  • Added unit tests for pinch-zoom functionality.
  • Verified the targeted unit tests pass.
  • Manually tested pinch-to-zoom on an Android device.
  • Verified the full test suite and CI checks.

Related Issue

Closes #2426

@github-actions github-actions Bot added the enhancement New feature or request label Oct 9, 2026
@greptile-apps

greptile-apps Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; the line-number preview issue is fixed and no new actionable defects were found.

Summary

Adds two-finger pinch-to-zoom to the main editor, with size limits and one settings update when the gesture ends.

  • The latest revision moves the preview to the editor root so code and line numbers zoom together.
  • It updates the gesture tests to check the root.
  • The numbered previous finding is fixed. No new actionable issues were found.

Reviews (4) · Last reviewed commit: "fix: address pinch-to-zoom review feedba..." · Reviewed by Greptile

Comment thread src/cm/pinchZoom.ts Outdated
Comment thread src/cm/pinchZoom.ts Outdated
Comment thread src/cm/pinchZoom.ts Outdated
Comment thread src/cm/pinchZoom.ts Outdated
@gahanad

gahanad commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread src/cm/pinchZoom.ts Outdated
@gahanad

gahanad commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread src/cm/pinchZoom.ts Outdated
@gahanad

gahanad commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

@greptile

@bajrangCoder

bajrangCoder commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

There several accidental zoom in/zoom out

screen-20261011-164227.mp4

Comment thread src/cm/pinchZoom.ts

// The gesture received a real movement; remember the latest distance
// even when the preview itself is throttled.
gesture.moved = true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved is set on any touchmove, including sub-pixel finger jitter. On a real device a two-finger tap almost always emits at least one touchmove, so the guard in endPinch that's meant to protect taps (9.5px → 10px, 99px → 72px) basically never applies. The tests only cover the no-touchmove case. Could we set moved only once the distance changes past a small threshold (a few px)?

Comment thread src/cm/pinchZoom.ts
// even when the preview itself is throttled.
gesture.moved = true;
gesture.pendingDistance = touchDistance(
event.touches[0],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using touches[0]/touches[1] without tracking Touch.identifier means the finger pair can change mid-gesture: pinch with A+B, a third finger C lands (ignored since pinching), A lifts → touches.length === 2 so endPinch returns early, and the next move measures B–C against the A–B startDistance → font size jumps (and can be persisted). Suggest storing both identifiers on touchstart, looking them up on move, and ending the pinch when either disappears.

Comment thread src/cm/pinchZoom.ts
function onTouchStart(event: TouchEvent) {
if (gesture.pinching || event.touches.length < 2) return;

event.preventDefault();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only the second finger's touchstart gets preventDefault. The first finger's touchstart/pointer events still reach CodeMirror and touchSelectionMenu's capture-phase pointer handlers:

  • If the first finger moves slightly before the second lands, native scroll has started and subsequent touchmoves are cancelable=false → the editor scrolls and zooms.
  • With the quick-tools Shift/Ctrl modifier active, #onGlobalPointerDown captures a selection session for the second pointer; if it lifts within TAP_MAX_DISTANCE (common when only one finger moves), #commitPointerSelection extends/adds a selection.

Might be worth suppressing/cancelling those when a pinch starts.

Comment thread src/cm/pinchZoom.ts
);

const now = Date.now();
if (now - gesture.lastUpdate < ZOOM_THROTTLE_MS) return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leading-edge throttle with no trailing flush: if the fingers stop within 50ms of the last applied frame, the preview stays stale until touchend, then jumps to a size the user never previewed before it's saved. Coalescing via requestAnimationFrame (store pendingDistance, schedule one rAF, cancel in destroy) would be smoother and simpler.

Comment thread src/cm/pinchZoom.ts
// (rebuilding the font theme in every pane) and writes settings.json
// exactly once per gesture, before the inline preview is dropped below.
settings.value.fontSize = `${gesture.lastPx}px`;
settings.update(false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

settings.update() returns a promise (it awaits the settings.json write) that's neither awaited nor caught. If the write fails, it becomes an unhandled rejection; the editor already shows the new size but it silently reverts next launch. A .catch(...) would help.

Comment thread src/cm/pinchZoom.ts

const { dom } = view;

dom.addEventListener("touchstart", onTouchStart, { passive: false });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Listeners are on view.dom, which also contains the search panel, autocomplete/hover tooltips, and gutters. A two-finger gesture on any of those zooms the editor and preventDefault blocks two-finger scrolling inside scrollable tooltips/panels. Consider attaching to view.scrollDOM/contentDOM, or skipping targets inside .cm-panels / .cm-tooltip.

Comment thread src/cm/pinchZoom.ts
// value, so writing settings mid-gesture can never undo a preview once
// the pinch returns to its starting size, and would leave the font
// theme stale as soon as the inline preview is removed.
view.dom.style.fontSize = `${px}px`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing view.dom.style.fontSize outside CM's update cycle relies on the contentDOM ResizeObserver to re-measure, which skips onResize if the doc view updated in the last 75ms. If a selection/LSP update lands mid-pinch, cursor/selection layers and gutters can stay at stale geometry. Calling view.requestMeasure() after applying the inline size would make this deterministic.

Comment thread src/cm/pinchZoom.ts
import settings from "lib/settings";

const ZOOM_THROTTLE_MS = 50;
const MIN_FONT_SIZE = 6;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: these limits (and the px-parsing fallback) duplicate adjustFontSize in src/cm/commandRegistry.js (Math.min(72, Math.max(6, …)), parseInt(..., 10) || 12). Exporting the constants/clamp helper from one place and reusing it there would keep pinch and the font-size commands from drifting.

pushExtension(extensions, options.commandKeymapExtension);
pushExtension(extensions, options.themeExtension);
extensions.push(fixedHeightTheme);
extensions.push(pinchZoom());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: pinchZoom() calls ViewPlugin.define on every createMainEditorExtensions call (each file open / state recreate), producing a new plugin identity each time. A single module-level ViewPlugin constant (like searchMatchHighlighter below) would be cheaper and lets view.plugin(...) find it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community enhancement New feature or request

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

Request featured "Zoom"

3 participants