From a61a89fe86d9b620238044e939a78acc24b39670 Mon Sep 17 00:00:00 2001 From: Dimas Victor Date: Wed, 28 Oct 2026 18:21:18 +0800 Subject: [PATCH] fix(vcs): Allow selecting and copying read-only diff text (#126) Read-only revision panes inherited the workbench's user-select: none and had no tab stop, so text couldn't be selected with the mouse or keyboard or copied. The viewer content now allows text selection and read-only panes take focus; editing stays blocked. Once focusable, these panes picked up CodeMirror's dark-theme focused selection colour, which outranked the viewer's rule and made selected text unreadable in light themes; the viewer's selection rule now matches that specificity. Co-authored-by: dvictor357 <187467850+dvictor357@users.noreply.github.com> --- src/features/vcs/__tests__/diffOptions.test.ts | 16 ++++++++++++++++ src/features/vcs/cm.ts | 6 ++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/features/vcs/__tests__/diffOptions.test.ts b/src/features/vcs/__tests__/diffOptions.test.ts index 40b337d50..f3066b6e9 100644 --- a/src/features/vcs/__tests__/diffOptions.test.ts +++ b/src/features/vcs/__tests__/diffOptions.test.ts @@ -186,6 +186,22 @@ describe("diff options", () => { parent.remove(); }); + it("gives a read-only side a tab stop and leaves an editable side alone", () => { + const parent = document.createElement("div"); + document.body.appendChild(parent); + const mv = new MergeView({ + a: { doc: "old\n", extensions: viewerBase({ readOnly: true }) }, + b: { doc: "new\n", extensions: viewerBase({ readOnly: false }) }, + parent, + }); + expect(mv.a.contentDOM.getAttribute("contenteditable")).toBe("false"); + expect(mv.a.contentDOM.getAttribute("tabindex")).toBe("0"); + expect(mv.b.contentDOM.getAttribute("contenteditable")).toBe("true"); + expect(mv.b.contentDOM.hasAttribute("tabindex")).toBe(false); + mv.destroy(); + parent.remove(); + }); + it("transfers a chunk between sides (>> / << / Revert)", () => { const a = doc("x\nold\ny\n"); const b = doc("x\nnew\nnewer\ny\n"); diff --git a/src/features/vcs/cm.ts b/src/features/vcs/cm.ts index 8e4dd3272..e1e912113 100644 --- a/src/features/vcs/cm.ts +++ b/src/features/vcs/cm.ts @@ -100,9 +100,9 @@ export const viewerTheme = EditorView.theme( fontSize: "var(--fs-code)", }, ".cm-scroller": { fontFamily: "var(--font-mono)", fontWeight: "var(--fw-code, 400)", lineHeight: "var(--lh-code)" }, - ".cm-content": { caretColor: "var(--accent)" }, + ".cm-content": { caretColor: "var(--accent)", WebkitUserSelect: "text", userSelect: "text" }, ".cm-cursor, .cm-dropCursor": { borderLeftColor: "var(--accent)" }, - "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection": { + "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, ::selection": { backgroundColor: "var(--bg-selected-strong)", }, ".cm-activeLine": { backgroundColor: "var(--bg-current-line)" }, @@ -180,6 +180,8 @@ export function viewerBase(opts: { readOnly: boolean; lineNumbers?: boolean }): wholeChangedLines, EditorState.readOnly.of(opts.readOnly), EditorView.editable.of(!opts.readOnly), + // Non-editable revision panes still need keyboard selection and copy. + opts.readOnly ? EditorView.contentAttributes.of({ tabindex: "0" }) : [], ]; }