/
raiden
/
obsidian-git-encrypt
Обзор
Документация
Войти
/
raiden
/
obsidian-git-encrypt
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/sourceControl/textDiff.ts
111 строк
4 KB
Robert Kuzhin
feat: add manual encrypted source control
09 авг 2026, 20:57
09 авг 2026, 20:57
3c8fa38
Код
Авторство
О чём код?
const MAX_TEXT_BYTES = 2 * 1024 * 1024; const MAX_LCS_CELLS = 4_000_000; const decoder = new TextDecoder("utf-8", { fatal: true }); export interface RenderedTextDiff { readonly binary: boolean; readonly text: string; readonly beforeText?: string; readonly afterText?: string; } export function renderUnifiedDiff( before: Uint8Array, after: Uint8Array, beforeLabel: string, afterLabel: string, ): RenderedTextDiff { const beforeText = decodeText(before); const afterText = decodeText(after); if (beforeText === undefined || afterText === undefined) { return { binary: true, text: "Binary file changed. Content diff is unavailable." }; } const oldLines = splitLines(beforeText); const newLines = splitLines(afterText); const operations = diffLines(oldLines, newLines); const lines = [`--- ${beforeLabel}`, `+++ ${afterLabel}`]; if (operations.every((operation) => operation.type === "equal")) { lines.push("No textual changes."); } else { lines.push(`@@ -1,${oldLines.length} +1,${newLines.length} @@`); for (const operation of operations) { const prefix = operation.type === "equal" ? " " : operation.type === "delete" ? "-" : "+"; for (const line of operation.lines) lines.push(`${prefix}${line}`); } } return { binary: false, text: lines.join("\n"), beforeText, afterText, }; } function decodeText(bytes: Uint8Array): string | undefined { if (bytes.length > MAX_TEXT_BYTES || bytes.includes(0)) return undefined; try { return decoder.decode(bytes); } catch { return undefined; } } function splitLines(value: string): string[] { if (value.length === 0) return []; const lines = value.replace(/\r\n?/gu, "\n").split("\n"); if (lines.at(-1) === "") lines.pop(); return lines; } interface DiffOperation { readonly type: "equal" | "delete" | "insert"; readonly lines: string[]; } function diffLines(before: readonly string[], after: readonly string[]): DiffOperation[] { if (before.length * after.length > MAX_LCS_CELLS) { return compact([ { type: "delete", lines: [...before] }, { type: "insert", lines: [...after] }, ]); } const rows = Array.from({ length: before.length + 1 }, () => new Uint32Array(after.length + 1)); for (let left = before.length - 1; left >= 0; left -= 1) { for (let right = after.length - 1; right >= 0; right -= 1) { rows[left]![right] = before[left] === after[right] ? rows[left + 1]![right + 1]! + 1 : Math.max(rows[left + 1]![right]!, rows[left]![right + 1]!); } } const operations: DiffOperation[] = []; let left = 0; let right = 0; while (left < before.length || right < after.length) { if (left < before.length && right < after.length && before[left] === after[right]) { operations.push({ type: "equal", lines: [before[left]!] }); left += 1; right += 1; } else if ( right >= after.length || (left < before.length && rows[left + 1]![right]! >= rows[left]![right + 1]!) ) { operations.push({ type: "delete", lines: [before[left]!] }); left += 1; } else { operations.push({ type: "insert", lines: [after[right]!] }); right += 1; } } return compact(operations); } function compact(operations: readonly DiffOperation[]): DiffOperation[] { const result: DiffOperation[] = []; for (const operation of operations) { if (operation.lines.length === 0) continue; const previous = result.at(-1); if (previous?.type === operation.type) previous.lines.push(...operation.lines); else result.push({ type: operation.type, lines: [...operation.lines] }); } return result; }