VST3: fetch midi mappings all at once, use it for note/sound-off
[carla.git] / source / modules / juce_core / text / juce_TextDiff.cpp
blob03f30fbbeb0ab39acfaa518eda1183ee93d09dff
1 /*
2 ==============================================================================
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
20 ==============================================================================
23 namespace juce
26 struct TextDiffHelpers
28 enum { minLengthToMatch = 3,
29 maxComplexity = 16 * 1024 * 1024 };
31 struct StringRegion
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;
42 int start, length;
45 static void addInsertion (TextDiff& td, String::CharPointerType text, int index, int length)
47 TextDiff::Change c;
48 c.insertedText = String (text, (size_t) length);
49 c.start = index;
50 c.length = 0;
51 td.changes.add (c);
54 static void addDeletion (TextDiff& td, int index, int length)
56 TextDiff::Change c;
57 c.start = index;
58 c.length = length;
59 td.changes.add (c);
62 static void diffSkippingCommonStart (TextDiff& td, StringRegion a, StringRegion b)
64 for (;;)
66 auto ca = *a.text;
67 auto cb = *b.text;
69 if (ca != cb || ca == 0)
70 break;
72 a.incrementStart();
73 b.incrementStart();
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));
90 else if (indexA > 0)
91 addDeletion (td, b.start, indexA);
92 else if (indexB > 0)
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));
98 else
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)
109 return 0;
111 if (lenA * lenB > maxComplexity)
112 return findCommonSuffix (a, lenA, indexInA,
113 b, lenB, indexInB);
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);
136 auto* l0 = lines;
137 auto* l1 = l0 + lenB + 1;
139 int loopsWithoutImprovement = 0;
140 int bestLength = 0;
142 for (int i = 0; i < lenA; ++i)
144 auto ca = a.getAndAdvance();
145 auto b2 = b;
147 for (int j = 0; j < lenB; ++j)
149 if (ca != b2.getAndAdvance())
151 l1[j + 1] = 0;
153 else
155 auto len = l0[j] + 1;
156 l1[j + 1] = len;
158 if (len > bestLength)
160 loopsWithoutImprovement = 0;
161 bestLength = len;
162 indexInA = i;
163 indexInB = j;
168 if (++loopsWithoutImprovement > 100)
169 break;
171 std::swap (l0, l1);
174 indexInA -= bestLength - 1;
175 indexInB -= bestLength - 1;
176 return bestLength;
179 static int findCommonSuffix (String::CharPointerType a, int lenA, int& indexInA,
180 String::CharPointerType b, int lenB, int& indexInB) noexcept
182 int length = 0;
183 a += lenA - 1;
184 b += lenB - 1;
186 while (length < lenA && length < lenB && *a == *b)
188 --a;
189 --b;
190 ++length;
193 indexInA = lenA - length;
194 indexInB = lenB - length;
195 return length;
199 TextDiff::TextDiff (const String& original, const String& target)
201 TextDiffHelpers::diffSkippingCommonStart (*this, original, target);
204 String TextDiff::appliedTo (String text) const
206 for (auto& c : changes)
207 text = c.appliedTo (text);
209 return text;
212 bool TextDiff::Change::isDeletion() const noexcept
214 return insertedText.isEmpty();
217 String TextDiff::Change::appliedTo (const String& text) const noexcept
219 return text.replaceSection (start, length, insertedText);
223 //==============================================================================
224 //==============================================================================
225 #if JUCE_UNIT_TESTS
227 class DiffTests : public UnitTest
229 public:
230 DiffTests()
231 : UnitTest ("TextDiff class", UnitTestCategories::text)
234 static String createString (Random& r)
236 juce_wchar buffer[500] = { 0 };
238 for (int i = r.nextInt (numElementsInArray (buffer) - 1); --i >= 0;)
240 if (r.nextInt (10) == 0)
244 buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
246 while (! CharPointer_UTF16::canRepresent (buffer[i]));
248 else
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");
271 testDiff ("x", "x");
272 testDiff ("x", "y");
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));
285 static DiffTests diffTests;
287 #endif
289 } // namespace juce