From 95405a07cbc2bd46063b19eef9a67e3326f60bca Mon Sep 17 00:00:00 2001 From: "Ioannis T." Date: Tue, 20 Oct 2026 14:12:21 +0300 Subject: [PATCH] Keep newlines when copying source lines Fixes 956f736acc78359f0966fc926aafaf5fb52978cd --- src/lib/syntax.ts | 15 ++++++++++----- tests/e2e/clipboard.spec.ts | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/lib/syntax.ts b/src/lib/syntax.ts index d74e6491b..261e78611 100644 --- a/src/lib/syntax.ts +++ b/src/lib/syntax.ts @@ -156,12 +156,17 @@ export function lineNumbersGutter(tree: Root) { }); } - // Add a line, and the eol. - lineNumber += 1; - replacement.push(createLine(line, lineNumber), { - type: "text", - value: match[0], + // Add a line, with its eol inside the line's content so that copying + // the line keeps it. The eol is wrapped in a span because the HTML + // parser drops a newline that directly follows a `
` start tag.
+        line.push({
+          type: "element",
+          tagName: "span",
+          properties: {},
+          children: [{ type: "text", value: match[0] }],
         });
+        lineNumber += 1;
+        replacement.push(createLine(line, lineNumber));
 
         start = index + 1;
         textStart = match.index + match[0].length;
diff --git a/tests/e2e/clipboard.spec.ts b/tests/e2e/clipboard.spec.ts
index 905355dfa..3c0b18390 100644
--- a/tests/e2e/clipboard.spec.ts
+++ b/tests/e2e/clipboard.spec.ts
@@ -87,6 +87,22 @@ test("copy to clipboard", async ({ page, browserName, context }) => {
     await expectClipboard("Inline `code` has `back-ticks around` it.\n");
   }
 
+  // Selected source lines keep their newlines, including an empty line.
+  {
+    await page.goto(`${markdownUrl}/tree/main/markdown-code.md#L1`);
+    const from = await page.locator("#L5").getByText("```").boundingBox();
+    const to = await page.locator("#L8").getByText("plain:").boundingBox();
+    if (!from || !to) {
+      throw new Error("source lines not rendered");
+    }
+    await page.mouse.move(from.x, from.y + from.height / 2);
+    await page.mouse.down();
+    await page.mouse.move(to.x, to.y + to.height / 2, { steps: 5 });
+    await page.mouse.up();
+    await page.keyboard.press("ControlOrMeta+C");
+    await expectClipboard("```\n\n```\n");
+  }
+
   // Link to a patch comment.
   {
     await page.goto(`${cobUrl}/patches`);