Файли
openidea/src/editor/core/clipboardHistory.ts
T
Jaw 55836e1b49 chore: Format the codebase with oxfmt and check formatting in CI (#135)
Run oxfmt once over the whole repository (formatting only, no logic changes) and add `pnpm format:check` to the frontend CI job so unformatted code no longer builds up. `.oxfmtrc.json` now skips the generated theme docs, test fixtures and code blocks inside Markdown.

Co-authored-by: jaw52 <[email protected]>
2026-10-28 19:35:16 +08:00

80 рядки
2.9 KiB
TypeScript
Неформатований Анотація Історія

Цей файл містить неоднозначні символи Юнікоду
Цей файл містить символи Юнікоду, які можна сплутати з іншими символами. Якщо ви вважаєте, що це зроблено навмисно, можете сміливо ігнорувати це попередження. Щоб показати їх, скористайтеся кнопкою Escape.
/**
* In-app clipboard history for Edit › Paste from History (⇧⌘V): text copied
* or cut in an editor (or elsewhere in the window), newest first, like
* IDEA's CopyPasteManager.
*/
import { EditorView } from "@codemirror/view";
import type { EditorState } from "@codemirror/state";
import { tx } from "@/platform/i18n";
export const MAX_CLIPBOARD_ENTRIES = 30;
let entries: string[] = [];
export function clipboardHistory(): readonly string[] {
return entries;
}
/** Adds `text` on top; an existing equal entry moves up instead of repeating. */
export function recordClipboard(text: string) {
if (!text) return;
entries = [text, ...entries.filter((e) => e !== text)].slice(0, MAX_CLIPBOARD_ENTRIES);
}
export function clearClipboardHistory() {
entries = [];
}
/** What CodeMirror copies: the selections, or the caret lines when nothing is selected. */
export function copiedText(state: EditorState): string {
const ranges = state.selection.ranges;
if (ranges.some((r) => !r.empty))
return ranges
.filter((r) => !r.empty)
.map((r) => state.sliceDoc(r.from, r.to))
.join(state.lineBreak);
const lines: string[] = [];
let last = -1;
for (const r of ranges) {
const line = state.doc.lineAt(r.head);
if (line.number === last) continue;
last = line.number;
lines.push(line.text);
}
return lines.join(state.lineBreak) + state.lineBreak;
}
/** Label of a history row: first non-blank line, shortened, plus the line count. */
export function clipLabel(text: string, max = 80): { label: string; detail: string | undefined } {
const lines = text.split(/\r\n?|\n/);
const first = (lines.find((l) => l.trim()) ?? "").trim();
const label = first.length > max ? `${first.slice(0, max - 1)}…` : first || "(whitespace)";
const count = text.endsWith("\n") ? lines.length - 1 : lines.length;
return { label, detail: count > 1 ? tx`${count} lines` : undefined };
}
export const clipboardHistoryExtension = EditorView.domEventHandlers({
copy: (_e, view) => void recordClipboard(copiedText(view.state)),
cut: (_e, view) => void recordClipboard(copiedText(view.state)),
});
/** Copies outside editors (inputs, tool windows) go into the history too. */
export function installClipboardTracking(): () => void {
if (typeof document === "undefined") return () => {};
const onCopy = (e: Event) => {
if (e.target instanceof Element && e.target.closest(".cm-editor")) return;
const t = e.target;
const text =
t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement
? t.value.slice(t.selectionStart ?? 0, t.selectionEnd ?? 0)
: (window.getSelection()?.toString() ?? "");
recordClipboard(text);
};
document.addEventListener("copy", onCopy);
document.addEventListener("cut", onCopy);
return () => {
document.removeEventListener("copy", onCopy);
document.removeEventListener("cut", onCopy);
};
}