Skip to content

feat: simpler text selection and Copy as Image - #6

Merged
jkyberneees merged 24 commits into
mainfrom
loop/feature-20261029-1232-copy-as-image
Oct 29, 2026
Merged

jkyberneees merged 24 commits into
mainfrom
loop/feature-20261029-1232-copy-as-image

Conversation

@jkyberneees

Copy link
Copy Markdown
Contributor

Simpler text selection and Copy as Image

What

  • Selection that survives re-renders. The code view keeps the selection as a model range (line/column, normalised start <= end). When the virtualised window re-renders, the DOM selection is restored from the range. Copy and Copy as Image read the model range, so they work beyond the rendered window.
  • File-wide select-all. Cmd/Ctrl+A in the code view selects the whole file as a model range. Plain copy and Copy as Image cover the whole file, subject to the size cap.
  • Clean copied text. Decorations (.trunc "… N more characters", gutter, git bars, blame) never reach copied text. .trunc is user-select: none.
  • Copy as Image, code view. Draws the selection to a PNG with syntax colours, theme background and foreground, the JetBrains Mono font, indentation and tabs, and partial first and last lines. No gutter by default; padding and rounded corners follow the theme. Canvas scale is min(devicePixelRatio, 2), reduced to stay under 16M pixels.
  • Copy as Image, markdown preview. Snapshots the rendered selection through an SVG foreignObject, with fonts and same-origin images embedded as data URIs. On failure it copies plain text and shows a message.
  • Entry points. Right-click menu ("Copy", "Copy as Image") when a selection exists; palette command "Copy Selection as Image"; shortcut ⌘⇧C / Ctrl+Shift+C (outside the terminal).
  • Feedback. "Copied as image", "Selection too large for an image", "Clipboard unavailable: image downloaded" (download fallback), "Could not create image; copied text instead", "Nothing selected".
  • Clipboard. ClipboardItem is built synchronously inside the user gesture, as WebKit requires. Falls back to downloading the PNG. The macOS app gets a native copyimage handler.
  • Docs. README and DESIGN.md (section 15) describe the feature and shortcut.

Why

User request: assess text selection from a user's point of view and add "Copy as image" on a selection that preserves the rendering format. The code view is virtualised, so a DOM-based copy would lose selections on scroll and re-render. The feature therefore draws from the model, and the markdown preview uses the DOM snapshot path.

AC to commit mapping

AC Commits
AC-1 range normalise, textOf, restorePlan, DOM restore a819947, 07c27e3, 915827b
AC-2 Cmd/Ctrl+A selects the whole file 07c27e3
AC-3 .trunc never copied 07c27e3
AC-4 code image layout (colours, tabs, partial lines, no gutter) 6f2aabe, dda8f24
AC-5 markdown snapshot (SVG foreignObject, fallback) 9d7e141, aa0915c, 35f13b1
AC-6 context menu, palette command, shortcut key helper 9e69713, 1b2419c, 1731044
AC-7 fitScale, size cap, feedback messages 6f2aabe, 39f6131
AC-7b exact message strings 39f6131
AC-8 clipboard in gesture, download and native fallbacks 39f6131, dcdda36
AC-9 README and DESIGN.md 9e69713, 1b2419c

How to test manually

  1. Code view. Open a file, select a few lines, then right-click and choose Copy as Image. Paste into an image-accepting app (Preview, Notes, Slack). Check the colours, font, indentation and partial first and last lines.
  2. Shortcut. Select code and press ⌘⇧C (macOS) or Ctrl+Shift+C (Linux/Windows). Confirm the "Copied as image" message. Pressing it inside the terminal must not trigger the feature.
  3. Palette. Open the command palette and run Copy Selection as Image.
  4. Survives scrolling. Select a range of around 10 to 300 lines, scroll out of the window and back. The selection should still be there, and copy should cover all of it.
  5. Select all. Focus the code view and press Cmd/Ctrl+A. The whole file should be selected. Copy as Image should show the file, or report "Selection too large for an image" if it exceeds the cap.
  6. Markdown preview. Select rendered text and use right-click, the palette, or the shortcut. Check the PNG against the on-screen rendering.
  7. Fallbacks. In a browser without ClipboardItem, the PNG should download and show "Clipboard unavailable: image downloaded". In the macOS app, the image should land on the pasteboard through the native handler.

Automated: bun scripts/build.ts --client-only && bun test (one suite at a time; concurrent runs flake).

Assumptions

  • A-1. Cmd/Ctrl+Shift+C is free in bindKeys. Terminal focus is excluded. In Chromium, outside the app, the shortcut may open devtools inspect; accepted.
  • A-2. Token colours are read with getComputedStyle from the --sx-* variables at copy time. Tests inject a theme object.
  • A-3. Image cap: 16M pixels of canvas area, and at most 20000 rows by 4000 columns. Assumed.
  • A-4. No typecheck or lint runs offline (tsc and a linter are unavailable), so lint and typecheck are omitted from commands.json.
  • A-5. Canvas, ClipboardItem and SVG-to-image glue lives in src/client/imagedom.ts, excluded from coverage and verified by hand. Everything else is unit-tested.
  • A-6. Tests run one suite at a time.
  • A-7. Real DOM selection restore, canvas and SVG rendering, clipboard writes and the Swift copyimage handler are checked by hand.

Known limitations

The findings first reported here (stale range, WebKit canvas taint, selection collapsing at click time, dead hook, untested font table and image rule) are fixed by the follow-up commits below. What remains:

  • Canvas, clipboard and SVG glue (imagedom.ts), the app wiring (app.ts, editor.ts) and the Swift handlers are checked by reading, not run in a real browser or WKWebView. Please try the manual steps above.
  • test/client.snapfallback4.test.ts sets window/document globals without teardown. The suite is green, but it depends on test order.
  • The macOS app keeps a few cancelled snapshot ids in memory after timeout races. Harmless.

Follow-up fixes (open review findings)

AC Fix Commits
AC-10 A stale code range is dropped when you select or click outside the code view. The find bar, scrollbar and gutter count as inside. c79b99a, ca9bb3a
AC-11 Right-click or Ctrl+click moves the active line and keeps the selection. c79b99a
AC-12 The dead context-menu hook is removed; the real wiring is tested via bindEditorSelectionMenu. c79b99a, 97d4efc
AC-13 The selection is captured when the context menu opens, and both menu items use that capture (a cloned Range). 97d4efc, da4bc20
AC-14 The font-face table moves into tested pure code; dedicated tests for isInlinableImage. 97d4efc
AC-15 macOS app: native WKWebView snapshot fallback when the WebKit canvas is tainted. Requests carry ids, reject non-finite rects and cancel late snapshots, and the selection highlight is hidden only by the request that owns it. 5374911, 894e016, 977106a, 0a1a5bc

🤖 Generated with Claude Code

CodeQL js/incomplete-multi-character-sanitization flagged a single-pass tag strip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… helper

The loop form was not recognised by CodeQL js/incomplete-multi-character-sanitization.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Failed ❌

View logs ↗
0a1a5bc 2026-10-29T13:51:56.469Z View logs ↗
  • Build: Failed ❌

View logs ↗
f030a86 2026-10-29T13:56:09.269Z View logs ↗
  • Build: In progress 🔵

View logs ↗
3076c1e 2026-10-29T13:57:37.410Z View logs ↗

…ping

CodeQL js/incomplete-multi-character-sanitization flags any regex tag strip here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jkyberneees
jkyberneees merged commit ccdeb00 into main Oct 29, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants