Text Diff Viewer — Java 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 Java 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: Java (17+). 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).
import java.util.ArrayList;
import java.util.List;
/** Line diff, ported from the canonical TS lib. */
public final class TextDiff {
enum Type { EQUAL, REMOVED, ADDED }
/** A merged run of consecutive same-type tokens (line tokens rejoin with '\n'). */
record Part(Type type, String text) {}
/** Content-only lines — the TS tokenizer's 'line' case: joining the tokens
* back with '\n' reconstructs the input; '' tokenizes to nothing. */
static List<String> tokenize(String text) {
List<String> out = new ArrayList<>();
if (text.isEmpty()) return out;
for (String line : text.split("\n", -1)) out.add(line); // -1 keeps trailing ""
return out;
}
/** 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. */
static List<Part> diff(String oldText, String newText) {
List<String> a = tokenize(oldText), b = tokenize(newText);
int n = a.size(), m = b.size();
int[][] 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.get(i).equals(b.get(j)) ? dp[i + 1][j + 1] + 1
: Math.max(dp[i + 1][j], dp[i][j + 1]);
List<Part> parts = new ArrayList<>(); // merge step: same-type runs rejoin '\n'
int i = 0, j = 0;
while (i < n || j < m) {
String tok; Type t;
if (i < n && j < m && a.get(i).equals(b.get(j))) { t = Type.EQUAL; tok = a.get(i); i++; j++; }
else if (j == m || (i < n && dp[i + 1][j] >= dp[i][j + 1])) { t = Type.REMOVED; tok = a.get(i++); }
else { t = Type.ADDED; tok = b.get(j++); }
if (!parts.isEmpty() && parts.get(parts.size() - 1).type() == t) {
Part last = parts.remove(parts.size() - 1);
parts.add(new Part(t, last.text() + "\n" + tok));
} else parts.add(new Part(t, tok));
}
return parts;
}
public static void main(String[] args) {
String a = "const x = 1;\nfunction greet(name) {\n return 'hi ' + name;\n}\nconsole.log(greet('dev'));";
String b = "const x = 2;\nfunction greet(name) {\n return 'hello, ' + name + '!';\n}\nconsole.log(greet('dev'));";
int add = 0, rem = 0, same = 0;
for (Part p : diff(a, b)) { // one prefix per line inside each part
char pre = p.type() == Type.ADDED ? '+' : p.type() == Type.REMOVED ? '-' : ' ';
for (String line : p.text().split("\n", -1)) System.out.println(pre + " " + line);
if (p.type() == Type.ADDED) add += p.text().length();
else if (p.type() == Type.REMOVED) rem += p.text().length();
else same += p.text().length();
}
System.out.printf("summary: +%d added, -%d removed, =%d unchanged chars%n", add, rem, same);
}
}
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 →