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++ (20). 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).
#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>
namespace text_diff {
enum class Type { Equal, Removed, Added };
struct Part { Type type; std::string text; }; // merged run of same-type lines
// Content-only lines — the TS tokenizer's 'line' case (text.split('\n')):
// joining the tokens back with '\n' reconstructs the input exactly.
std::vector<std::string> tokenize(const std::string& text) {
std::vector<std::string> lines;
if (text.empty()) return lines; // '' tokenizes to nothing, as in the TS
for (size_t pos = 0; pos <= text.size();) {
size_t nl = text.find('\n', pos);
size_t end = nl == std::string::npos ? text.size() : nl;
lines.emplace_back(text, pos, end - pos);
pos = nl == std::string::npos ? text.size() + 1 : nl + 1;
}
return lines;
}
// 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', matching the TS reference.
std::vector<Part> diff(const std::string& old_text, const std::string& new_text) {
auto a = tokenize(old_text), b = tokenize(new_text);
const size_t n = a.size(), m = b.size(), w = m + 1;
std::vector<size_t> dp((n + 1) * w, 0);
for (size_t i = n; i-- > 0;)
for (size_t j = m; j-- > 0;)
dp[i * w + j] = a[i] == b[j] ? dp[(i + 1) * w + j + 1] + 1
: std::max(dp[(i + 1) * w + j], dp[i * w + j + 1]);
std::vector<Part> parts; // merge step: same-type runs rejoin with '\n'
auto push = [&](Type t, const std::string& tok) {
if (!parts.empty() && parts.back().type == t) parts.back().text += "\n" + tok;
else parts.push_back({t, tok});
};
size_t i = 0, j = 0;
while (i < n || j < m) {
if (i < n && j < m && a[i] == b[j]) { push(Type::Equal, a[i]); i++; j++; }
else if (j == m || (i < n && dp[(i + 1) * w + j] >= dp[i * w + j + 1])) push(Type::Removed, a[i++]);
else push(Type::Added, b[j++]);
}
return parts;
}
} // namespace text_diff
int main() {
using namespace text_diff;
std::string a = "const x = 1;\nfunction greet(name) {\n return 'hi ' + name;\n}\nconsole.log(greet('dev'));";
std::string b = "const x = 2;\nfunction greet(name) {\n return 'hello, ' + name + '!';\n}\nconsole.log(greet('dev'));";
size_t add = 0, rem = 0, same = 0;
for (const Part& p : diff(a, b)) { // one prefix per line inside each part
char pre = p.type == Type::Added ? '+' : p.type == Type::Removed ? '-' : ' ';
for (size_t pos = 0; pos <= p.text.size();) {
size_t nl = p.text.find('\n', pos);
size_t end = nl == std::string::npos ? p.text.size() : nl;
std::printf("%c %.*s\n", pre, int(end - pos), p.text.data() + pos);
pos = nl == std::string::npos ? p.text.size() + 1 : nl + 1;
}
if (p.type == Type::Added) add += p.text.size();
else if (p.type == Type::Removed) rem += p.text.size();
else same += p.text.size();
}
std::printf("summary: +%zu added, -%zu removed, =%zu 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 →