28 enum { minLengthToMatch = 3,
29 maxComplexity = 16 * 1024 * 1024 };
33 StringRegion (
const String& s) noexcept
34 : text (s.getCharPointer()), start (0), length (s.length()) {}
36 StringRegion (String::CharPointerType t,
int s,
int len) noexcept
37 : text (t), start (s), length (len) {}
39 void incrementStart() noexcept { ++text; ++start; --length; }
41 String::CharPointerType text;
45 static void addInsertion (TextDiff& td, String::CharPointerType text,
int index,
int length)
48 c.insertedText = String (text, (
size_t) length);
54 static void addDeletion (TextDiff& td,
int index,
int length)
62 static void diffSkippingCommonStart (TextDiff& td, StringRegion a, StringRegion b)
69 if (ca != cb || ca == 0)
76 diffRecursively (td, a, b);
79 static void diffRecursively (TextDiff& td, StringRegion a, StringRegion b)
81 int indexA = 0, indexB = 0;
82 auto len = findLongestCommonSubstring (a.text, a.length, indexA,
83 b.text, b.length, indexB);
85 if (len >= minLengthToMatch)
87 if (indexA > 0 && indexB > 0)
88 diffSkippingCommonStart (td, StringRegion (a.text, a.start, indexA),
89 StringRegion (b.text, b.start, indexB));
91 addDeletion (td, b.start, indexA);
93 addInsertion (td, b.text, b.start, indexB);
95 diffRecursively (td, StringRegion (a.text + (indexA + len), a.start + indexA + len, a.length - indexA - len),
96 StringRegion (b.text + (indexB + len), b.start + indexB + len, b.length - indexB - len));
100 if (a.length > 0) addDeletion (td, b.start, a.length);
101 if (b.length > 0) addInsertion (td, b.text, b.start, b.length);
105 static int findLongestCommonSubstring (String::CharPointerType a,
const int lenA,
int& indexInA,
106 String::CharPointerType b,
const int lenB,
int& indexInB)
noexcept
108 if (lenA == 0 || lenB == 0)
111 if (lenA * lenB > maxComplexity)
112 return findCommonSuffix (a, lenA, indexInA,
115 auto scratchSpace =
sizeof (int) * (2 + 2 * (
size_t) lenB);
117 if (scratchSpace < 4096)
119 JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6255)
120 auto* scratch = (
int*) alloca (scratchSpace);
121 JUCE_END_IGNORE_WARNINGS_MSVC
123 return findLongestCommonSubstring (a, lenA, indexInA, b, lenB, indexInB, scratchSpace, scratch);
126 HeapBlock<
int> scratch (scratchSpace);
127 return findLongestCommonSubstring (a, lenA, indexInA, b, lenB, indexInB, scratchSpace, scratch);
130 static
int findLongestCommonSubstring (String::CharPointerType a, const
int lenA,
int& indexInA,
131 String::CharPointerType b, const
int lenB,
int& indexInB,
132 const
size_t scratchSpace,
int* const lines) noexcept
134 zeromem (lines, scratchSpace);
137 auto* l1 = l0 + lenB + 1;
139 int loopsWithoutImprovement = 0;
142 for (
int i = 0; i < lenA; ++i)
144 auto ca = a.getAndAdvance();
147 for (
int j = 0; j < lenB; ++j)
149 if (ca != b2.getAndAdvance())
155 auto len = l0[j] + 1;
158 if (len > bestLength)
160 loopsWithoutImprovement = 0;
168 if (++loopsWithoutImprovement > 100)
174 indexInA -= bestLength - 1;
175 indexInB -= bestLength - 1;
179 static int findCommonSuffix (String::CharPointerType a,
int lenA,
int& indexInA,
180 String::CharPointerType b,
int lenB,
int& indexInB)
noexcept
186 while (length < lenA && length < lenB && *a == *b)
193 indexInA = lenA - length;
194 indexInB = lenB - length;
201 TextDiffHelpers::diffSkippingCommonStart (*
this, original, target);
206 for (
auto& c : changes)
207 text = c.appliedTo (text);
219 return text.replaceSection (start, length, insertedText);
227class DiffTests final :
public UnitTest
231 :
UnitTest (
"TextDiff class", UnitTestCategories::text)
236 juce_wchar buffer[500] = { 0 };
238 for (
int i = r.
nextInt (numElementsInArray (buffer) - 1); --i >= 0;)
244 buffer[i] = (juce_wchar) (1 + r.
nextInt (0x10ffff - 1));
246 while (! CharPointer_UTF16::canRepresent (buffer[i]));
249 buffer[i] = (juce_wchar) (
'a' + r.
nextInt (3));
252 return CharPointer_UTF32 (buffer);
255 void testDiff (
const String& a,
const String& b)
257 TextDiff diff (a, b);
258 auto result = diff.appliedTo (a);
259 expectEquals (result, b);
262 void runTest()
override
264 beginTest (
"TextDiff");
266 auto r = getRandom();
268 testDiff (String(), String());
269 testDiff (
"x", String());
270 testDiff (String(),
"x");
273 testDiff (
"xxx",
"x");
274 testDiff (
"x",
"xxx");
276 for (
int i = 1000; --i >= 0;)
278 auto s = createString (r);
279 testDiff (s, createString (r));
280 testDiff (s + createString (r), s + createString (r));
285static DiffTests diffTests;
bool isEmpty() const noexcept
TextDiff(const String &original, const String &target)
String appliedTo(String text) const
String appliedTo(const String &original) const noexcept
bool isDeletion() const noexcept