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.

239 lines
7.5KB

  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. if (indexA > 0 && indexB > 0)
  69. diffSkippingCommonStart (td, StringRegion (a.text, a.start, indexA),
  70. StringRegion (b.text, b.start, indexB));
  71. else if (indexA > 0)
  72. addDeletion (td, b.start, indexA);
  73. else if (indexB > 0)
  74. addInsertion (td, b.text, b.start, indexB);
  75. diffRecursively (td, StringRegion (a.text + indexA + len, a.start + indexA + len, a.length - indexA - len),
  76. StringRegion (b.text + indexB + len, b.start + indexB + len, b.length - indexB - len));
  77. }
  78. else
  79. {
  80. if (a.length > 0) addDeletion (td, b.start, a.length);
  81. if (b.length > 0) addInsertion (td, b.text, b.start, b.length);
  82. }
  83. }
  84. static int findLongestCommonSubstring (String::CharPointerType a, const int lenA,
  85. const String::CharPointerType& b, const int lenB,
  86. int& indexInA, int& indexInB)
  87. {
  88. if (lenA == 0 || lenB == 0)
  89. return 0;
  90. HeapBlock<int> lines;
  91. lines.calloc (2 + 2 * (size_t) lenB);
  92. int* l0 = lines;
  93. int* l1 = l0 + lenB + 1;
  94. int bestLength = 0;
  95. indexInA = indexInB = 0;
  96. for (int i = 0; i < lenA; ++i)
  97. {
  98. const juce_wchar ca = a.getAndAdvance();
  99. String::CharPointerType b2 (b);
  100. for (int j = 0; j < lenB; ++j)
  101. {
  102. if (ca != b2.getAndAdvance())
  103. {
  104. l1[j + 1] = 0;
  105. }
  106. else
  107. {
  108. const int len = l0[j] + 1;
  109. l1[j + 1] = len;
  110. if (len > bestLength)
  111. {
  112. bestLength = len;
  113. indexInA = i;
  114. indexInB = j;
  115. }
  116. }
  117. }
  118. std::swap (l0, l1);
  119. }
  120. indexInA -= bestLength - 1;
  121. indexInB -= bestLength - 1;
  122. return bestLength;
  123. }
  124. };
  125. TextDiff::TextDiff (const String& original, const String& target)
  126. {
  127. TextDiffHelpers::diffSkippingCommonStart (*this, original, target);
  128. }
  129. String TextDiff::appliedTo (String text) const
  130. {
  131. for (int i = 0; i < changes.size(); ++i)
  132. text = changes.getReference(i).appliedTo (text);
  133. return text;
  134. }
  135. bool TextDiff::Change::isDeletion() const noexcept
  136. {
  137. return insertedText.isEmpty();
  138. }
  139. String TextDiff::Change::appliedTo (const String& text) const noexcept
  140. {
  141. return text.substring (0, start) + (isDeletion() ? text.substring (start + length)
  142. : (insertedText + text.substring (start)));
  143. }
  144. //==============================================================================
  145. //==============================================================================
  146. #if JUCE_UNIT_TESTS
  147. class DiffTests : public UnitTest
  148. {
  149. public:
  150. DiffTests() : UnitTest ("TextDiff class") {}
  151. static String createString()
  152. {
  153. juce_wchar buffer[50] = { 0 };
  154. Random r;
  155. for (int i = r.nextInt (49); --i >= 0;)
  156. {
  157. if (r.nextInt (10) == 0)
  158. {
  159. do
  160. {
  161. buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
  162. }
  163. while (! CharPointer_UTF16::canRepresent (buffer[i]));
  164. }
  165. else
  166. buffer[i] = (juce_wchar) ('a' + r.nextInt (3));
  167. }
  168. return CharPointer_UTF32 (buffer);
  169. }
  170. void testDiff (const String& a, const String& b)
  171. {
  172. TextDiff diff (a, b);
  173. const String result (diff.appliedTo (a));
  174. expectEquals (result, b);
  175. }
  176. void runTest()
  177. {
  178. beginTest ("TextDiff");
  179. testDiff (String::empty, String::empty);
  180. testDiff ("x", String::empty);
  181. testDiff (String::empty, "x");
  182. testDiff ("x", "x");
  183. testDiff ("x", "y");
  184. testDiff ("xxx", "x");
  185. testDiff ("x", "xxx");
  186. for (int i = 5000; --i >= 0;)
  187. {
  188. String s (createString());
  189. testDiff (s, createString());
  190. testDiff (s + createString(), s + createString());
  191. }
  192. }
  193. };
  194. static DiffTests diffTests;
  195. #endif