Text Diff Viewer — C# 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 C# 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: C# (.NET 7+). 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).
using System;
using System.Collections.Generic;
namespace TextDiff;
public enum DiffType { Equal, Removed, Added }
/// <summary>A merged run of consecutive same-type tokens (lines rejoin with '\n').</summary>
public readonly record struct Part(DiffType Type, string Text);
public static class TextDiffTool
{
// Content-only lines — the TS tokenizer's 'line' case: joining with '\n'
// reconstructs the input exactly; '' tokenizes to nothing.
static List<string> Tokenize(string text) =>
text == "" ? new List<string>() : new List<string>(text.Split('\n'));
// 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.
public static List<Part> Diff(string oldText, string newText)
{
var a = Tokenize(oldText); var b = Tokenize(newText);
int n = a.Count, m = b.Count;
var dp = new int[n + 1, m + 1];
for (int i = n - 1; i >= 0; i--)
for (int j = m - 1; j >= 0; j--)
dp[i, j] = a[i] == b[j] ? dp[i + 1, j + 1] + 1 : Math.Max(dp[i + 1, j], dp[i, j + 1]);
var parts = new List<Part>(); // merge step: same-type runs rejoin '\n'
void Push(DiffType t, string tok)
{
if (parts.Count > 0 && parts[^1].Type == t)
parts[^1] = new Part(t, parts[^1].Text + "\n" + tok);
else parts.Add(new Part(t, tok));
}
int i = 0, j = 0;
while (i < n || j < m)
{
if (i < n && j < m && a[i] == b[j]) { Push(DiffType.Equal, a[i]); i++; j++; }
else if (j == m || (i < n && dp[i + 1, j] >= dp[i, j + 1])) Push(DiffType.Removed, a[i++]);
else Push(DiffType.Added, b[j++]);
}
return parts;
}
}
public static class Demo
{
public static void Main()
{
var a = "const x = 1;\nfunction greet(name) {\n return 'hi ' + name;\n}\nconsole.log(greet('dev'));";
var b = "const x = 2;\nfunction greet(name) {\n return 'hello, ' + name + '!';\n}\nconsole.log(greet('dev'));";
int add = 0, rem = 0, same = 0;
foreach (var p in TextDiffTool.Diff(a, b)) // one prefix per line per part
{
char pre = p.Type == DiffType.Added ? '+' : p.Type == DiffType.Removed ? '-' : ' ';
foreach (var line in p.Text.Split('\n')) Console.WriteLine($"{pre} {line}");
if (p.Type == DiffType.Added) add += p.Text.Length;
else if (p.Type == DiffType.Removed) rem += p.Text.Length;
else same += p.Text.Length;
}
Console.WriteLine($"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 →