fix(clipboard): write CRLF line endings on Windows copies
This commit is contained in:
1 parent
1b6ff2ba54
commit
78f9740366
4 files changed
+68
-6
No files matched your search
+43
-2
@@ -1,6 +1,10 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { copyToClipboard, type ClipboardEnvironment } from "@/lib/common/clipboard";
|
||||
import { clipboardLineEndings, copyToClipboard, type ClipboardEnvironment } from "@/lib/common/clipboard";
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
function legacyClipboardEnvironment(activeDialog?: { appendChild: ReturnType<typeof vi.fn>; removeChild: ReturnType<typeof vi.fn> }) {
|
||||
const textarea = {
|
||||
@@ -45,4 +49,41 @@ describe("copyToClipboard", () => {
|
||||
expect(body.appendChild).toHaveBeenCalledWith(textarea);
|
||||
expect(body.removeChild).toHaveBeenCalledWith(textarea);
|
||||
});
|
||||
|
||||
it("writes CRLF line endings on Windows", async () => {
|
||||
vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
|
||||
const writeText = vi.fn();
|
||||
|
||||
await copyToClipboard("SELECT 1\nFROM t\nWHERE x = 1", { navigator: { clipboard: { writeText } } });
|
||||
|
||||
expect(writeText).toHaveBeenCalledWith("SELECT 1\r\nFROM t\r\nWHERE x = 1");
|
||||
});
|
||||
|
||||
it("keeps LF line endings off Windows", async () => {
|
||||
vi.spyOn(navigator, "userAgent", "get").mockReturnValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)");
|
||||
const writeText = vi.fn();
|
||||
|
||||
await copyToClipboard("SELECT 1\nFROM t", { navigator: { clipboard: { writeText } } });
|
||||
|
||||
expect(writeText).toHaveBeenCalledWith("SELECT 1\nFROM t");
|
||||
});
|
||||
});
|
||||
|
||||
describe("clipboardLineEndings", () => {
|
||||
it("converts bare LF and CR to CRLF on Windows", () => {
|
||||
expect(clipboardLineEndings("a\nb\rc", "windows")).toBe("a\r\nb\r\nc");
|
||||
});
|
||||
|
||||
it("leaves text that already uses CRLF untouched", () => {
|
||||
expect(clipboardLineEndings("a\r\nb\r\n", "windows")).toBe("a\r\nb\r\n");
|
||||
});
|
||||
|
||||
it("leaves single-line text untouched on Windows", () => {
|
||||
expect(clipboardLineEndings("SELECT 1", "windows")).toBe("SELECT 1");
|
||||
});
|
||||
|
||||
it("returns the text unchanged on other platforms", () => {
|
||||
expect(clipboardLineEndings("a\nb", "macos")).toBe("a\nb");
|
||||
expect(clipboardLineEndings("a\nb", "linux")).toBe("a\nb");
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
||||
import { getPlatform, type Platform } from "@/lib/backend/platform";
|
||||
|
||||
interface ClipboardApi {
|
||||
readText?: () => Promise<string> | string;
|
||||
@@ -42,6 +43,21 @@ export interface ClipboardEnvironment {
|
||||
document?: ClipboardDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites the line endings of text destined for the system clipboard.
|
||||
*
|
||||
* Windows native edit controls (the Win32 EDIT control, and applications built
|
||||
* on it such as PowerBuilder) only treat CRLF as a line break, so text copied
|
||||
* with bare LF arrives as a single line. Other platforms keep LF.
|
||||
*
|
||||
* Existing CRLF pairs are matched before bare CR and LF so that already
|
||||
* normalized text is left byte-identical.
|
||||
*/
|
||||
export function clipboardLineEndings(text: string, platform: Platform = getPlatform()): string {
|
||||
if (platform !== "windows") return text;
|
||||
return text.replace(/\r\n|\r|\n/g, "\r\n");
|
||||
}
|
||||
|
||||
export interface ClipboardShortcutEvent {
|
||||
key: string;
|
||||
metaKey?: boolean;
|
||||
@@ -136,6 +152,8 @@ export async function readTextFromClipboard(env: ClipboardEnvironment = globalTh
|
||||
}
|
||||
|
||||
export async function copyToClipboard(text: string, env: ClipboardEnvironment = globalThis as unknown as ClipboardEnvironment): Promise<void> {
|
||||
text = clipboardLineEndings(text);
|
||||
|
||||
if (isTauriRuntime(env as unknown as Record<string, unknown>)) {
|
||||
try {
|
||||
const { writeText } = await import("@tauri-apps/plugin-clipboard-manager");
|
||||
|
||||
Reference in new issue
Block a user