(oldText: string, newText: string, contextLines = 3)
| 71 | * Returns "" when the contents are identical. |
| 72 | */ |
| 73 | export function unifiedDiff(oldText: string, newText: string, contextLines = 3): string { |
| 74 | if (oldText === newText) return "" |
| 75 | // An empty file is zero lines, not one empty line. |
| 76 | const ops = diffLines(oldText === "" ? [] : oldText.split("\n"), newText === "" ? [] : newText.split("\n")) |
| 77 | |
| 78 | const out: string[] = [] |
| 79 | let oldLine = 1 |
| 80 | let newLine = 1 |
| 81 | let hunk: string[] = [] |
| 82 | let hunkOldStart = 1 |
| 83 | let hunkNewStart = 1 |
| 84 | let hunkOldCount = 0 |
| 85 | let hunkNewCount = 0 |
| 86 | let trailingContext = 0 |
| 87 | |
| 88 | const flush = () => { |
| 89 | if (hunk.length === 0) return |
| 90 | // Drop context beyond the hunk's trailing window. |
| 91 | const extra = Math.max(0, trailingContext - contextLines) |
| 92 | if (extra > 0) { |
| 93 | hunk = hunk.slice(0, hunk.length - extra) |
| 94 | hunkOldCount -= extra |
| 95 | hunkNewCount -= extra |
| 96 | } |
| 97 | out.push(`@@ -${hunkOldStart},${hunkOldCount} +${hunkNewStart},${hunkNewCount} @@`) |
| 98 | out.push(...hunk) |
| 99 | hunk = [] |
| 100 | hunkOldCount = 0 |
| 101 | hunkNewCount = 0 |
| 102 | trailingContext = 0 |
| 103 | } |
| 104 | |
| 105 | let pendingContext: string[] = [] |
| 106 | for (const op of ops) { |
| 107 | if (op.type === " ") { |
| 108 | if (hunk.length > 0) { |
| 109 | hunk.push(` ${op.text}`) |
| 110 | hunkOldCount++ |
| 111 | hunkNewCount++ |
| 112 | trailingContext++ |
| 113 | if (trailingContext > contextLines * 2) flush() |
| 114 | } else { |
| 115 | pendingContext.push(op.text) |
| 116 | if (pendingContext.length > contextLines) pendingContext.shift() |
| 117 | } |
| 118 | oldLine++ |
| 119 | newLine++ |
| 120 | } else { |
| 121 | if (hunk.length === 0) { |
| 122 | hunkOldStart = oldLine - pendingContext.length |
| 123 | hunkNewStart = newLine - pendingContext.length |
| 124 | hunk = pendingContext.map((text) => ` ${text}`) |
| 125 | hunkOldCount = pendingContext.length |
| 126 | hunkNewCount = pendingContext.length |
| 127 | pendingContext = [] |
| 128 | } |
| 129 | trailingContext = 0 |
| 130 | hunk.push(`${op.type}${op.text}`) |
no test coverage detected