Text Diff Viewer — Swift source
Compare two pieces of text and see exactly what changed. Highlights added and removed lines, words, or characters, shows a per-side summary, and exports a unified diff you can paste into a PR or commit. Runs 100% in your browser.
This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// text-diff — line-granularity diff via an LCS dynamic-programming table. Language: Swift (5.9+). Port of src/lib/text-diff.ts — core tokenizer/backwards-DP/greedy-walk/run-merge; word/char granularity, normalization options and unified hunk headers live in this dir's javascript.js (80-line budget).
enum DiffType { case equal, removed, added }
/// A merged run of consecutive same-type tokens (line tokens rejoin with '\n').
struct DiffPart { var type: DiffType; var text: String }
/// Content-only lines — the TS tokenizer's 'line' case: joining the tokens
/// back with '\n' reconstructs the input exactly; '' tokenizes to nothing.
func tokenize(_ text: String) -> [String] {
text.isEmpty ? [] : text.split(separator: "\n", omittingEmptySubsequences: false).map(String.init)
}
/// dp[i][j] = LCS length of a[i..] and b[j..], built backwards. The greedy walk
/// emits an equal part on token match, else drops the side whose remaining LCS
/// is larger — the '>=' tie favors 'removed', as in the TS reference.
func diff(_ oldText: String, _ newText: String) -> [DiffPart] {
let a = tokenize(oldText), b = tokenize(newText)
let n = a.count, m = b.count
var dp = [[Int]](repeating: [Int](repeating: 0, count: m + 1), count: n + 1)
for i in stride(from: n - 1, through: 0, by: -1) {
for j in stride(from: m - 1, through: 0, by: -1) {
dp[i][j] = a[i] == b[j] ? dp[i + 1][j + 1] + 1 : max(dp[i + 1][j], dp[i][j + 1])
}
}
var parts: [DiffPart] = [] // merge step: same-type runs rejoin with '\n'
func push(_ t: DiffType, _ tok: String) {
if let last = parts.last, last.type == t {
parts[parts.count - 1].text += "\n" + tok
} else { parts.append(DiffPart(type: t, text: tok)) }
}
var i = 0, j = 0
while i < n || j < m {
if i < n && j < m && a[i] == b[j] { push(.equal, a[i]); i += 1; j += 1 }
else if j == m || (i < n && dp[i + 1][j] >= dp[i][j + 1]) { push(.removed, a[i]); i += 1 }
else { push(.added, b[j]); j += 1 }
}
return parts
}
let a = "const x = 1;\nfunction greet(name) {\n return 'hi ' + name;\n}\nconsole.log(greet('dev'));"
let b = "const x = 2;\nfunction greet(name) {\n return 'hello, ' + name + '!';\n}\nconsole.log(greet('dev'));"
var add = 0, rem = 0, same = 0
for p in diff(a, b) { // one prefix per line inside each part
let pre = p.type == .added ? "+" : p.type == .removed ? "-" : " "
for line in p.text.split(separator: "\n", omittingEmptySubsequences: false) { print("\(pre) \(line)") }
switch p.type {
case .added: add += p.text.count
case .removed: rem += p.text.count
case .equal: same += p.text.count
}
}
print("summary: +\(add) added, -\(rem) removed, =\(same) unchanged chars")
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →