diff --git a/ivette/package.json b/ivette/package.json
index 5916f2832104af4dc270adf23249704bce3ee5b4..4accc25c115927804617213092dd1a8ebf2aead9 100644
--- a/ivette/package.json
+++ b/ivette/package.json
@@ -55,6 +55,7 @@
   "dependencies": {
     "@babel/runtime": "^7.9.2",
     "@fortawesome/fontawesome-free": "^5.13.1",
+    "@types/diff": "^4.0.2",
     "@types/react-window": "^1.8.2",
     "codemirror": "^5.52.2",
     "cytoscape": "^3.15.1",
@@ -65,6 +66,7 @@
     "cytoscape-klay": "^3.1.3",
     "cytoscape-panzoom": "^2.5.3",
     "cytoscape-popper": "^1.0.7",
+    "diff": "^5.0.0",
     "immutable": "^4.0.0-rc.12",
     "lodash": "^4.17.15",
     "react": "^16.8",
diff --git a/ivette/src/frama-c/eva/diffed.tsx b/ivette/src/frama-c/eva/diffed.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..27f4e6819430a9ba5bc57b9cb64862f7b467a282
--- /dev/null
+++ b/ivette/src/frama-c/eva/diffed.tsx
@@ -0,0 +1,67 @@
+// --------------------------------------------------------------------------
+// --- Diff Text Rendering
+// --------------------------------------------------------------------------
+
+import React from 'react';
+import { Change, diffChars } from 'diff';
+
+export class DiffBuffer {
+
+  private readonly contents: React.ReactNode[] = [];
+  private added = false;
+  private removed = false;
+  private value = '';
+
+  constructor() {
+    this.push = this.push.bind(this);
+  }
+
+  push(c: Change) {
+    if (!c.added && !c.removed) this.flush();
+    if (c.added) this.added = true;
+    if (c.removed) this.removed = true;
+    if (!c.added) this.value += c.value;
+  }
+
+  flush(): React.ReactNode[] {
+    const { value, added, removed, contents } = this;
+    if (value) {
+      if (added && removed) {
+        contents.push(<span className='eva-diff-modified'>{value}</span>);
+      } else if (removed) {
+        contents.push(<span className='eva-diff-removed'>{value}</span>);
+      } else if (added) {
+        contents.push(<span className='eva-diff-added' />);
+      } else {
+        contents.push(value);
+      }
+      this.value = '';
+      this.added = false;
+      this.removed = false;
+    }
+    return this.contents;
+  }
+
+}
+
+/* --------------------------------------------------------------------------*/
+/* --- Component with memoized diff                                       ---*/
+/* --------------------------------------------------------------------------*/
+
+export interface DiffedProps {
+  original?: string;
+  modified?: string;
+}
+
+export function Diffed(props: DiffedProps) {
+  const { original = '', modified = '' } = props;
+  const contents = React.useMemo(() => {
+    if (original === modified) return original;
+    const buffer = new DiffBuffer();
+    diffChars(original, modified).forEach(buffer.push);
+    return buffer.flush();
+  }, [original, modified]);
+  return <>{contents}</>;
+}
+
+// --------------------------------------------------------------------------
diff --git a/ivette/yarn.lock b/ivette/yarn.lock
index 1b2c21e55881c5a5a6630ade9f9dd6f6499ff8ec..d1b9cd89c54e0e40fff49ff63ea303bc37d47555 100644
--- a/ivette/yarn.lock
+++ b/ivette/yarn.lock
@@ -1022,6 +1022,11 @@
   resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
   integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
 
+"@types/diff@^4.0.2":
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/@types/diff/-/diff-4.0.2.tgz#2e9bb89f9acc3ab0108f0f3dc4dbdcf2fff8a99c"
+  integrity sha512-mIenTfsIe586/yzsyfql69KRnA75S8SVXQbTLpDejRrjH0QSJcpu3AUOi/Vjnt9IOsXKxPhJfGpQUNMueIU1fQ==
+
 "@types/eslint-visitor-keys@^1.0.0":
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
@@ -3240,6 +3245,11 @@ detect-node@^2.0.4:
   resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
   integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
 
+diff@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
+  integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
+
 diffie-hellman@^5.0.0:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"