Skip to content

Commit d61820b

Browse files
committed
Fix diff section alignment
1 parent bed5cdd commit d61820b

1 file changed

Lines changed: 132 additions & 45 deletions

File tree

Tool/Sources/CodeDiff/CodeDiff.swift

Lines changed: 132 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -95,51 +95,11 @@ public struct CodeDiff {
9595
let oldLines = oldSnippet.splitByNewLine(omittingEmptySubsequences: false)
9696
let diffByLine = newLines.difference(from: oldLines)
9797

98-
struct DiffSection: Equatable {
99-
var offset: Int
100-
var end: Int
101-
var lines: [String]
102-
}
103-
104-
func collect(
105-
into all: inout [DiffSection],
106-
changes: [CollectionDifference<Substring>.Change],
107-
extract: (CollectionDifference<Substring>.Change) -> (offset: Int, line: Substring)?
108-
) {
109-
var current: DiffSection?
110-
for change in changes {
111-
guard let (offset, element) = extract(change) else { continue }
112-
if var section = current {
113-
if offset == section.end + 1 {
114-
section.lines.append(String(element))
115-
section.end = offset
116-
current = section
117-
continue
118-
} else {
119-
all.append(section)
120-
}
121-
}
122-
123-
current = DiffSection(offset: offset, end: offset, lines: [String(element)])
124-
}
125-
126-
if let current {
127-
all.append(current)
128-
}
129-
}
130-
131-
var insertions = [DiffSection]()
132-
var removals = [DiffSection]()
133-
134-
collect(into: &removals, changes: diffByLine.removals) { change in
135-
guard case let .remove(offset, element, _) = change else { return nil }
136-
return (offset, element)
137-
}
138-
139-
collect(into: &insertions, changes: diffByLine.insertions) { change in
140-
guard case let .insert(offset, element, _) = change else { return nil }
141-
return (offset, element)
142-
}
98+
let (insertions, removals) = generateDiffSections(
99+
oldLines: oldLines,
100+
newLines: newLines,
101+
diffByLine: diffByLine
102+
)
143103

144104
var oldLineIndex = 0
145105
var newLineIndex = 0
@@ -217,6 +177,133 @@ public struct CodeDiff {
217177
}
218178
}
219179

180+
extension CodeDiff {
181+
struct DiffSection: Equatable {
182+
var offset: Int
183+
var end: Int
184+
var lines: [String]
185+
186+
mutating func appendIfPossible(offset: Int, element: Substring) -> Bool {
187+
if end + 1 != offset { return false }
188+
end = offset
189+
lines.append(String(element))
190+
return true
191+
}
192+
}
193+
194+
func generateDiffSections(
195+
oldLines: [Substring],
196+
newLines: [Substring],
197+
diffByLine: CollectionDifference<Substring>
198+
) -> (insertionSections: [DiffSection], removalSections: [DiffSection]) {
199+
let insertionDiffs = diffByLine.insertions
200+
let removalDiffs = diffByLine.removals
201+
var insertions = [DiffSection]()
202+
var removals = [DiffSection]()
203+
var insertionIndex = 0
204+
var removalIndex = 0
205+
var insertionUnchangedGap = 0
206+
var removalUnchangedGap = 0
207+
208+
while insertionIndex < insertionDiffs.endIndex || removalIndex < removalDiffs.endIndex {
209+
let insertion = insertionDiffs[safe: insertionIndex]
210+
let removal = removalDiffs[safe: removalIndex]
211+
212+
append(
213+
into: &insertions,
214+
change: insertion,
215+
index: &insertionIndex,
216+
unchangedGap: &insertionUnchangedGap
217+
) { change in
218+
guard case let .insert(offset, element, _) = change else { return nil }
219+
return (offset, element)
220+
}
221+
222+
append(
223+
into: &removals,
224+
change: removal,
225+
index: &removalIndex,
226+
unchangedGap: &removalUnchangedGap
227+
) { change in
228+
guard case let .remove(offset, element, _) = change else { return nil }
229+
return (offset, element)
230+
}
231+
232+
if insertionUnchangedGap > removalUnchangedGap {
233+
// insert empty sections to insertions
234+
if removalUnchangedGap > 0 {
235+
let count = insertionUnchangedGap - removalUnchangedGap
236+
let index = max(insertions.endIndex - 1, 0)
237+
let offset = (insertions.last?.offset ?? 0) - count
238+
insertions.insert(
239+
.init(offset: offset, end: offset, lines: []),
240+
at: index
241+
)
242+
insertionUnchangedGap -= removalUnchangedGap
243+
removalUnchangedGap = 0
244+
} else if removal == nil {
245+
removalUnchangedGap = 0
246+
insertionUnchangedGap = 0
247+
}
248+
} else if removalUnchangedGap > insertionUnchangedGap {
249+
// insert empty sections to removals
250+
if insertionUnchangedGap > 0 {
251+
let count = removalUnchangedGap - insertionUnchangedGap
252+
let index = max(removals.endIndex - 1, 0)
253+
let offset = (removals.last?.offset ?? 0) - count
254+
removals.insert(
255+
.init(offset: offset, end: offset, lines: []),
256+
at: index
257+
)
258+
removalUnchangedGap -= insertionUnchangedGap
259+
insertionUnchangedGap = 0
260+
} else {
261+
removalUnchangedGap = 0
262+
insertionUnchangedGap = 0
263+
}
264+
} else {
265+
removalUnchangedGap = 0
266+
insertionUnchangedGap = 0
267+
}
268+
}
269+
270+
return (insertions, removals)
271+
}
272+
273+
func append(
274+
into sections: inout [DiffSection],
275+
change: CollectionDifference<Substring>.Change?,
276+
index: inout Int,
277+
unchangedGap: inout Int,
278+
extract: (CollectionDifference<Substring>.Change) -> (offset: Int, line: Substring)?
279+
) {
280+
guard let change, let (offset, element) = extract(change) else { return }
281+
if unchangedGap == 0 {
282+
if !sections.isEmpty {
283+
let lastIndex = sections.endIndex - 1
284+
if !sections[lastIndex]
285+
.appendIfPossible(offset: offset, element: element)
286+
{
287+
unchangedGap = offset - sections[lastIndex].end - 1
288+
sections.append(.init(
289+
offset: offset,
290+
end: offset,
291+
lines: [String(element)]
292+
))
293+
}
294+
} else {
295+
sections.append(.init(
296+
offset: offset,
297+
end: offset,
298+
lines: [String(element)]
299+
))
300+
unchangedGap = offset
301+
}
302+
index += 1
303+
}
304+
}
305+
}
306+
220307
extension Array {
221308
subscript(safe index: Int) -> Element? {
222309
guard index >= 0, index < count else { return nil }

0 commit comments

Comments
 (0)