дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
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]>
80 рядки
2.9 KiB
TypeScript
80 рядки
2.9 KiB
TypeScript
/**
|
||
* 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);
|
||
};
|
||
}
|