8
9
10
11
12
13
21using namespace Qt::StringLiterals;
27 const auto [it1, it2] = std::mismatch(text1.cbegin(), text1.cend(),
28 text2.cbegin(), text2.cend());
30 return qsizetype(std::distance(text1.cbegin(), it1));
36 const auto [it1, it2] = std::mismatch(text1.crbegin(), text1.crend(),
37 text2.crbegin(), text2.crend());
39 return qsizetype(std::distance(text1.crbegin(), it1));
44 QList<Diff> newDiffList;
45 newDiffList.reserve(diffList.size());
46 for (
const Diff &diff : diffList) {
48 for (QChar c : diff.text) {
49 const qsizetype idx =
static_cast<ushort>(c.unicode());
50 text += lines.value(idx);
52 newDiffList.append({diff.command, text});
59 if (diffList.size() < 3)
62 QList<Diff> newDiffList;
63 Diff prevDiff = diffList.at(0);
64 Diff thisDiff = diffList.at(1);
65 Diff nextDiff = diffList.at(2);
67 while (i < diffList.size()) {
70 if (thisDiff.text.endsWith(prevDiff.text)) {
71 thisDiff.text = prevDiff.text
72 + thisDiff.text.left(thisDiff.text.size()
73 - prevDiff.text.size());
74 nextDiff.text = prevDiff.text + nextDiff.text;
75 }
else if (thisDiff.text.startsWith(nextDiff.text)) {
76 prevDiff.text += nextDiff.text;
77 thisDiff.text = thisDiff.text.mid(nextDiff.text.size())
80 if (i < diffList.size())
81 nextDiff = diffList.at(i);
82 newDiffList.append(prevDiff);
84 newDiffList.append(prevDiff);
87 newDiffList.append(prevDiff);
92 if (i < diffList.size())
93 nextDiff = diffList.at(i);
95 newDiffList.append(prevDiff);
96 if (i == diffList.size())
97 newDiffList.append(thisDiff);
111 return command == other.command && text == other.text;
127 m_currentDiffMode = m_diffMode;
128 return merge(preprocess1AndDiff(text1, text2));
141QList<Diff>
Differ::preprocess1AndDiff(
const QString &text1,
const QString &text2)
143 if (text1.isNull() && text2.isNull())
146 if (text1 == text2) {
147 QList<Diff> diffList;
148 if (!text1.isEmpty())
153 QString newText1 = text1;
154 QString newText2 = text2;
157 const qsizetype prefixCount = commonPrefix(text1, text2);
159 prefix = text1.left(prefixCount);
160 newText1 = text1.mid(prefixCount);
161 newText2 = text2.mid(prefixCount);
163 const qsizetype suffixCount = commonSuffix(newText1, newText2);
165 suffix = newText1.right(suffixCount);
166 newText1 = newText1.left(newText1.size() - suffixCount);
167 newText2 = newText2.left(newText2.size() - suffixCount);
169 QList<Diff> diffList = preprocess2AndDiff(newText1, newText2);
177QList<Diff>
Differ::preprocess2AndDiff(
const QString &text1,
const QString &text2)
179 QList<Diff> diffList;
181 if (text1.isEmpty()) {
186 if (text2.isEmpty()) {
191 if (text1.size() != text2.size()) {
192 const QString longtext = text1.size() > text2.size() ? text1 : text2;
193 const QString shorttext = text1.size() > text2.size() ? text2 : text1;
194 const qsizetype i = longtext.indexOf(shorttext);
196 const Diff::
Command command = (text1.size() > text2.size())
198 diffList.append(
Diff(command, longtext.left(i)));
200 diffList.append(
Diff(command, longtext.mid(i + shorttext.size())));
204 if (shorttext.size() == 1) {
211 if (m_currentDiffMode != Differ::CharMode && text1.size() > 80 && text2.size() > 80)
212 return diffNonCharMode(text1, text2);
214 return diffMyers(text1, text2);
217QList<Diff>
Differ::diffMyers(
const QString &text1,
const QString &text2)
219 const qsizetype n = text1.size();
220 const qsizetype m = text2.size();
221 const bool odd = (n + m) % 2;
222 const qsizetype D = odd ? (n + m) / 2 + 1 : (n + m) / 2;
223 const qsizetype delta = n - m;
224 const qsizetype vShift = D;
225 std::vector<qsizetype> forwardV(2 * D + 1);
226 std::vector<qsizetype> reverseV(2 * D + 1);
227 for (qsizetype i = 0; i <= 2 * D; i++) {
231 forwardV[vShift + 1] = 0;
232 reverseV[vShift + 1] = 0;
233 qsizetype kMinForward = -D;
234 qsizetype kMaxForward = D;
235 qsizetype kMinReverse = -D;
236 qsizetype kMaxReverse = D;
237 for (qsizetype d = 0; d <= D; d++) {
239 for (qsizetype k = qMax(-d, kMinForward + qAbs(d + kMinForward) % 2);
240 k <= qMin(d, kMaxForward - qAbs(d + kMaxForward) % 2);
243 if (k == -d || (k < d && forwardV[k + vShift - 1] < forwardV[k + vShift + 1]))
244 x = forwardV[k + vShift + 1];
246 x = forwardV[k + vShift - 1] + 1;
255 while (x < n && y < m) {
256 if (text1.at(x) != text2.at(y))
261 forwardV[k + vShift] = x;
263 if (k >= delta - (d - 1) && k <= delta + (d - 1)) {
264 if (n - reverseV[delta - k + vShift] <= x) {
265 return diffMyersSplit(text1, x, text2, y);
272 for (qsizetype k = qMax(-d, kMinReverse + qAbs(d + kMinReverse) % 2);
273 k <= qMin(d, kMaxReverse - qAbs(d + kMaxReverse) % 2);
276 if (k == -d || (k < d && reverseV[k + vShift - 1] < reverseV[k + vShift + 1]))
277 x = reverseV[k + vShift + 1];
279 x = reverseV[k + vShift - 1] + 1;
288 while (x < n && y < m) {
289 if (text1.at(n - x - 1) != text2.at(m - y - 1))
294 reverseV[k + vShift] = x;
296 if (k >= delta - d && k <= delta + d) {
297 if (n - forwardV[delta - k + vShift] <= x) {
298 return diffMyersSplit(text1, n - x, text2, m - x + k);
306 QList<Diff> diffList;
312QList<Diff>
Differ::diffMyersSplit(
313 const QString &text1, qsizetype x,
314 const QString &text2, qsizetype y)
316 const QString text11 = text1.left(x);
317 const QString text12 = text1.mid(x);
318 const QString text21 = text2.left(y);
319 const QString text22 = text2.mid(y);
321 const QList<Diff> &diffList1 = preprocess1AndDiff(text11, text21);
322 const QList<Diff> &diffList2 = preprocess1AndDiff(text12, text22);
323 return diffList1 + diffList2;
326QList<Diff>
Differ::diffNonCharMode(
const QString &text1,
const QString &text2)
328 QString encodedText1;
329 QString encodedText2;
330 QStringList subtexts = encode(text1, text2, &encodedText1, &encodedText2);
332 DiffMode diffMode = m_currentDiffMode;
337 QList<Diff> diffList = preprocess1AndDiff(encodedText1, encodedText2);
339 diffList = decode(diffList, subtexts);
343 QList<Diff> newDiffList;
344 for (qsizetype i = 0; i <= diffList.size(); i++) {
345 const Diff diffItem = i < diffList.size()
350 lastDelete += diffItem.text;
352 lastInsert += diffItem.text;
354 if (!(lastDelete.isEmpty() && lastInsert.isEmpty())) {
356 newDiffList += preprocess1AndDiff(lastDelete, lastInsert);
361 newDiffList.append(diffItem);
365 m_currentDiffMode = diffMode;
369QStringList
Differ::encode(
const QString &text1,
370 const QString &text2,
371 QString *encodedText1,
372 QString *encodedText2)
374 QStringList lines{{}};
375 QHash<QString, qsizetype> lineToCode;
377 *encodedText1 = encode(text1, &lines, &lineToCode);
378 *encodedText2 = encode(text2, &lines, &lineToCode);
383qsizetype
Differ::findSubtextEnd(
const QString &text,
384 qsizetype subtextStart)
387 qsizetype subtextEnd = text.indexOf(u'\n', subtextStart);
388 if (subtextEnd == -1)
389 subtextEnd = text.size() - 1;
392 if (!text.at(subtextStart).isLetter())
393 return subtextStart + 1;
394 qsizetype i = subtextStart + 1;
396 const qsizetype count = text.size();
397 while (i < count && text.at(i).isLetter())
401 return subtextStart + 1;
404QString
Differ::encode(
const QString &text,
406 QHash<QString, qsizetype> *lineToCode)
408 qsizetype subtextStart = 0;
409 qsizetype subtextEnd = -1;
411 while (subtextEnd < text.size()) {
412 subtextEnd = findSubtextEnd(text, subtextStart);
413 const QString line = text.mid(subtextStart, subtextEnd - subtextStart);
414 subtextStart = subtextEnd;
416 if (lineToCode->contains(line)) {
417 codes += QChar(
static_cast<ushort>(lineToCode->value(line)));
420 lineToCode->insert(line, lines->size() - 1);
421 codes += QChar(
static_cast<ushort>(lines->size() - 1));
427QList<Diff>
Differ::merge(
const QList<Diff> &diffList)
431 QList<Diff> newDiffList;
432 for (qsizetype i = 0; i <= diffList.size(); i++) {
433 Diff diff = i < diffList.size()
438 lastDelete += diff.text;
440 lastInsert += diff.text;
442 if (!(lastDelete.isEmpty() && lastInsert.isEmpty())) {
445 const qsizetype prefixCount = commonPrefix(lastDelete, lastInsert);
447 const QString prefix = lastDelete.left(prefixCount);
448 lastDelete = lastDelete.mid(prefixCount);
449 lastInsert = lastInsert.mid(prefixCount);
451 if (!newDiffList.isEmpty()
453 newDiffList.last().text += prefix;
460 const qsizetype suffixCount = commonSuffix(lastDelete, lastInsert);
462 const QString suffix = lastDelete.right(suffixCount);
463 lastDelete = lastDelete.left(lastDelete.size() - suffixCount);
464 lastInsert = lastInsert.left(lastInsert.size() - suffixCount);
466 diff.text.prepend(suffix);
470 if (!lastDelete.isEmpty())
472 if (!lastInsert.isEmpty())
474 if (!diff.text.isEmpty())
475 newDiffList.append(diff);
479 if (!newDiffList.isEmpty()
481 newDiffList.last().text += diff.text;
483 if (!diff.text.isEmpty())
484 newDiffList.append(diff);
490 QList<Diff> squashedDiffList = squashEqualities(newDiffList);
491 if (squashedDiffList.size() != newDiffList.size())
492 return merge(squashedDiffList);
494 return squashedDiffList;
bool operator==(const Diff &other) const
bool operator!=(const Diff &other) const
Diff(Command com, const QString &txt={})
QList< Diff > diff(const QString &text1, const QString &text2)
DiffMode diffMode() const
void setDiffMode(DiffMode mode)
static QList< Diff > decode(const QList< Diff > &diffList, const QStringList &lines)
static qsizetype commonSuffix(const QString &text1, const QString &text2)
static qsizetype commonPrefix(const QString &text1, const QString &text2)
static QList< Diff > squashEqualities(const QList< Diff > &diffList)
Combined button and popup list for selecting options.