The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

243 lines
7.7KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. struct TextDiffHelpers
  19. {
  20. enum { minLengthToMatch = 3 };
  21. struct StringRegion
  22. {
  23. StringRegion (const String& s) noexcept
  24. : text (s.getCharPointer()), start (0), length (s.length()) {}
  25. StringRegion (const String::CharPointerType& t, int s, int len) noexcept
  26. : text (t), start (s), length (len) {}
  27. String::CharPointerType text;
  28. int start, length;
  29. };
  30. static void addInsertion (TextDiff& td, const String::CharPointerType& text, int index, int length)
  31. {
  32. TextDiff::Change c;
  33. c.insertedText = String (text, (size_t) length);
  34. c.start = index;
  35. c.length = length;
  36. td.changes.add (c);
  37. }
  38. static void addDeletion (TextDiff& td, int index, int length)
  39. {
  40. TextDiff::Change c;
  41. c.start = index;
  42. c.length = length;
  43. td.changes.add (c);
  44. }
  45. static void diffSkippingCommonStart (TextDiff& td, const StringRegion& a, const StringRegion& b)
  46. {
  47. String::CharPointerType sa (a.text);
  48. String::CharPointerType sb (b.text);
  49. const int maxLen = jmax (a.length, b.length);
  50. for (int i = 0; i < maxLen; ++i, ++sa, ++sb)
  51. {
  52. if (*sa != *sb)
  53. {
  54. diffRecursively (td, StringRegion (sa, a.start + i, a.length - i),
  55. StringRegion (sb, b.start + i, b.length - i));
  56. break;
  57. }
  58. }
  59. }
  60. static void diffRecursively (TextDiff& td, const StringRegion& a, const StringRegion& b)
  61. {
  62. int indexA, indexB;
  63. const int len = findLongestCommonSubstring (a.text, a.length,
  64. b.text, b.length,
  65. indexA, indexB);
  66. if (len >= minLengthToMatch)
  67. {
  68. jassert (indexA >= 0 && indexA <= a.length);
  69. jassert (indexB >= 0 && indexB <= b.length);
  70. jassert (String (a.text + indexA, (size_t) len) == String (b.text + indexB, (size_t) len));
  71. if (indexA > 0 && indexB > 0)
  72. diffSkippingCommonStart (td, StringRegion (a.text, a.start, indexA),
  73. StringRegion (b.text, b.start, indexB));
  74. else if (indexA > 0)
  75. addDeletion (td, b.start, indexA);
  76. else if (indexB > 0)
  77. addInsertion (td, b.text, b.start, indexB);
  78. diffRecursively (td, StringRegion (a.text + indexA + len, a.start + indexA + len, a.length - indexA - len),
  79. StringRegion (b.text + indexB + len, b.start + indexB + len, b.length - indexB - len));
  80. }
  81. else
  82. {
  83. if (a.length > 0) addDeletion (td, b.start, a.length);
  84. if (b.length > 0) addInsertion (td, b.text, b.start, b.length);
  85. }
  86. }
  87. static int findLongestCommonSubstring (String::CharPointerType a, const int lenA,
  88. const String::CharPointerType& b, const int lenB,
  89. int& indexInA, int& indexInB)
  90. {
  91. if (lenA == 0 || lenB == 0)
  92. return 0;
  93. HeapBlock<int> lines;
  94. lines.calloc (2 + 2 * (size_t) lenB);
  95. int* l0 = lines;
  96. int* l1 = l0 + lenB + 1;
  97. int bestLength = 0;
  98. indexInA = indexInB = 0;
  99. for (int i = 0; i < lenA; ++i)
  100. {
  101. const juce_wchar ca = a.getAndAdvance();
  102. String::CharPointerType b2 (b);
  103. for (int j = 0; j < lenB; ++j)
  104. {
  105. if (ca != b2.getAndAdvance())
  106. {
  107. l1[j + 1] = 0;
  108. }
  109. else
  110. {
  111. const int len = l0[j] + 1;
  112. l1[j + 1] = len;
  113. if (len > bestLength)
  114. {
  115. bestLength = len;
  116. indexInA = i;
  117. indexInB = j;
  118. }
  119. }
  120. }
  121. std::swap (l0, l1);
  122. }
  123. indexInA -= bestLength - 1;
  124. indexInB -= bestLength - 1;
  125. return bestLength;
  126. }
  127. };
  128. TextDiff::TextDiff (const String& original, const String& target)
  129. {
  130. TextDiffHelpers::diffSkippingCommonStart (*this, original, target);
  131. }
  132. String TextDiff::appliedTo (String text) const
  133. {
  134. for (int i = 0; i < changes.size(); ++i)
  135. text = changes.getReference(i).appliedTo (text);
  136. return text;
  137. }
  138. bool TextDiff::Change::isDeletion() const noexcept
  139. {
  140. return insertedText.isEmpty();
  141. }
  142. String TextDiff::Change::appliedTo (const String& text) const noexcept
  143. {
  144. return text.substring (0, start) + (isDeletion() ? text.substring (start + length)
  145. : (insertedText + text.substring (start)));
  146. }
  147. //==============================================================================
  148. //==============================================================================
  149. #if JUCE_UNIT_TESTS
  150. class DiffTests : public UnitTest
  151. {
  152. public:
  153. DiffTests() : UnitTest ("TextDiff class") {}
  154. static String createString()
  155. {
  156. juce_wchar buffer[50] = { 0 };
  157. Random r;
  158. for (int i = r.nextInt (49); --i >= 0;)
  159. {
  160. if (r.nextInt (10) == 0)
  161. {
  162. do
  163. {
  164. buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
  165. }
  166. while (! CharPointer_UTF16::canRepresent (buffer[i]));
  167. }
  168. else
  169. buffer[i] = (juce_wchar) ('a' + r.nextInt (3));
  170. }
  171. return CharPointer_UTF32 (buffer);
  172. }
  173. void testDiff (const String& a, const String& b)
  174. {
  175. TextDiff diff (a, b);
  176. const String result (diff.appliedTo (a));
  177. expectEquals (result, b);
  178. }
  179. void runTest()
  180. {
  181. beginTest ("TextDiff");
  182. testDiff (String::empty, String::empty);
  183. testDiff ("x", String::empty);
  184. testDiff (String::empty, "x");
  185. testDiff ("x", "x");
  186. testDiff ("x", "y");
  187. testDiff ("xxx", "x");
  188. testDiff ("x", "xxx");
  189. for (int i = 5000; --i >= 0;)
  190. {
  191. String s (createString());
  192. testDiff (s, createString());
  193. testDiff (s + createString(), s + createString());
  194. }
  195. }
  196. };
  197. static DiffTests diffTests;
  198. #endif