8
9
10
11
12
13
24using namespace Qt::StringLiterals;
30 const auto [it1, it2] =
std::mismatch(text1.cbegin(), text1.cend(),
31 text2.cbegin(), text2.cend());
33 return qsizetype(
std::distance(text1.cbegin(), it1));
39 const auto [it1, it2] =
std::mismatch(text1.crbegin(), text1.crend(),
40 text2.crbegin(), text2.crend());
42 return qsizetype(
std::distance(text1.crbegin(), it1));
47 QList<Diff> newDiffList;
48 newDiffList.reserve(diffList.size());
49 for (
const Diff &diff : diffList) {
51 for (QChar c : diff.text) {
52 const qsizetype idx =
static_cast<ushort>(c.unicode());
53 text += lines.value(idx);
55 newDiffList.append({diff.command, text});
62 if (diffList.size() < 3)
65 QList<Diff> newDiffList;
66 Diff prevDiff = diffList.at(0);
67 Diff thisDiff = diffList.at(1);
68 Diff nextDiff = diffList.at(2);
70 while (i < diffList.size()) {
73 if (thisDiff.text.endsWith(prevDiff.text)) {
74 thisDiff.text = prevDiff.text
75 + thisDiff.text.left(thisDiff.text.size()
76 - prevDiff.text.size());
77 nextDiff.text = prevDiff.text + nextDiff.text;
78 }
else if (thisDiff.text.startsWith(nextDiff.text)) {
79 prevDiff.text += nextDiff.text;
80 thisDiff.text = thisDiff.text.mid(nextDiff.text.size())
83 if (i < diffList.size())
84 nextDiff = diffList.at(i);
85 newDiffList.append(prevDiff);
87 newDiffList.append(prevDiff);
90 newDiffList.append(prevDiff);
95 if (i < diffList.size())
96 nextDiff = diffList.at(i);
98 newDiffList.append(prevDiff);
99 if (i == diffList.size())
100 newDiffList.append(thisDiff);
114 return command == other.command && text == other.text;
130 m_currentDiffMode = m_diffMode;
131 return merge(preprocess1AndDiff(text1, text2));
144QList<Diff>
Differ::preprocess1AndDiff(
const QString &text1,
const QString &text2)
146 if (text1.isNull() && text2.isNull())
149 if (text1 == text2) {
150 QList<Diff> diffList;
151 if (!text1.isEmpty())
156 QString newText1 = text1;
157 QString newText2 = text2;
160 const qsizetype prefixCount = commonPrefix(text1, text2);
162 prefix = text1.left(prefixCount);
163 newText1 = text1.mid(prefixCount);
164 newText2 = text2.mid(prefixCount);
166 const qsizetype suffixCount = commonSuffix(newText1, newText2);
168 suffix = newText1.right(suffixCount);
169 newText1 = newText1.left(newText1.size() - suffixCount);
170 newText2 = newText2.left(newText2.size() - suffixCount);
172 QList<Diff> diffList = preprocess2AndDiff(newText1, newText2);
180QList<Diff>
Differ::preprocess2AndDiff(
const QString &text1,
const QString &text2)
182 QList<Diff> diffList;
184 if (text1.isEmpty()) {
189 if (text2.isEmpty()) {
194 if (text1.size() != text2.size()) {
195 const QString longtext = text1.size() > text2.size() ? text1 : text2;
196 const QString shorttext = text1.size() > text2.size() ? text2 : text1;
197 const qsizetype i = longtext.indexOf(shorttext);
199 const Diff::
Command command = (text1.size() > text2.size())
201 diffList.append(
Diff(command, longtext.left(i)));
203 diffList.append(
Diff(command, longtext.mid(i + shorttext.size())));
207 if (shorttext.size() == 1) {
214 if (m_currentDiffMode != Differ::CharMode && text1.size() > 80 && text2.size() > 80)
215 return diffNonCharMode(text1, text2);
217 return diffMyers(text1, text2);
220QList<Diff>
Differ::diffMyers(
const QString &text1,
const QString &text2)
222 const qsizetype n = text1.size();
223 const qsizetype m = text2.size();
224 const bool odd = (n + m) % 2;
225 const qsizetype D = odd ? (n + m) / 2 + 1 : (n + m) / 2;
226 const qsizetype delta = n - m;
227 const qsizetype vShift = D;
228 std::vector<qsizetype> forwardV(2 * D + 1);
229 std::vector<qsizetype> reverseV(2 * D + 1);
230 for (qsizetype i = 0; i <= 2 * D; i++) {
234 forwardV[vShift + 1] = 0;
235 reverseV[vShift + 1] = 0;
236 qsizetype kMinForward = -D;
237 qsizetype kMaxForward = D;
238 qsizetype kMinReverse = -D;
239 qsizetype kMaxReverse = D;
240 for (qsizetype d = 0; d <= D; d++) {
242 for (qsizetype k = qMax(-d, kMinForward + qAbs(d + kMinForward) % 2);
243 k <= qMin(d, kMaxForward - qAbs(d + kMaxForward) % 2);
246 if (k == -d || (k < d && forwardV[k + vShift - 1] < forwardV[k + vShift + 1]))
247 x = forwardV[k + vShift + 1];
249 x = forwardV[k + vShift - 1] + 1;
258 while (x < n && y < m) {
259 if (text1.at(x) != text2.at(y))
264 forwardV[k + vShift] = x;
266 if (k >= delta - (d - 1) && k <= delta + (d - 1)) {
267 if (n - reverseV[delta - k + vShift] <= x) {
268 return diffMyersSplit(text1, x, text2, y);
275 for (qsizetype k = qMax(-d, kMinReverse + qAbs(d + kMinReverse) % 2);
276 k <= qMin(d, kMaxReverse - qAbs(d + kMaxReverse) % 2);
279 if (k == -d || (k < d && reverseV[k + vShift - 1] < reverseV[k + vShift + 1]))
280 x = reverseV[k + vShift + 1];
282 x = reverseV[k + vShift - 1] + 1;
291 while (x < n && y < m) {
292 if (text1.at(n - x - 1) != text2.at(m - y - 1))
297 reverseV[k + vShift] = x;
299 if (k >= delta - d && k <= delta + d) {
300 if (n - forwardV[delta - k + vShift] <= x) {
301 return diffMyersSplit(text1, n - x, text2, m - x + k);
309 QList<Diff> diffList;
315QList<Diff>
Differ::diffMyersSplit(
316 const QString &text1, qsizetype x,
317 const QString &text2, qsizetype y)
319 const QString text11 = text1.left(x);
320 const QString text12 = text1.mid(x);
321 const QString text21 = text2.left(y);
322 const QString text22 = text2.mid(y);
324 const QList<Diff> &diffList1 = preprocess1AndDiff(text11, text21);
325 const QList<Diff> &diffList2 = preprocess1AndDiff(text12, text22);
326 return diffList1 + diffList2;
329QList<Diff>
Differ::diffNonCharMode(
const QString &text1,
const QString &text2)
331 QString encodedText1;
332 QString encodedText2;
333 QStringList subtexts = encode(text1, text2, &encodedText1, &encodedText2);
335 DiffMode diffMode = m_currentDiffMode;
340 QList<Diff> diffList = preprocess1AndDiff(encodedText1, encodedText2);
342 diffList = decode(diffList, subtexts);
346 QList<Diff> newDiffList;
347 for (qsizetype i = 0; i <= diffList.size(); i++) {
348 const Diff diffItem = i < diffList.size()
353 lastDelete += diffItem.text;
355 lastInsert += diffItem.text;
357 if (!(lastDelete.isEmpty() && lastInsert.isEmpty())) {
359 newDiffList += preprocess1AndDiff(lastDelete, lastInsert);
364 newDiffList.append(diffItem);
368 m_currentDiffMode = diffMode;
372QStringList
Differ::encode(
const QString &text1,
373 const QString &text2,
374 QString *encodedText1,
375 QString *encodedText2)
377 QStringList lines{{}};
378 QHash<QString, qsizetype> lineToCode;
380 *encodedText1 = encode(text1, &lines, &lineToCode);
381 *encodedText2 = encode(text2, &lines, &lineToCode);
386qsizetype
Differ::findSubtextEnd(
const QString &text,
387 qsizetype subtextStart)
390 qsizetype subtextEnd = text.indexOf(u'\n', subtextStart);
391 if (subtextEnd == -1)
392 subtextEnd = text.size() - 1;
395 if (!text.at(subtextStart).isLetter())
396 return subtextStart + 1;
397 qsizetype i = subtextStart + 1;
399 const qsizetype count = text.size();
400 while (i < count && text.at(i).isLetter())
404 return subtextStart + 1;
407QString
Differ::encode(
const QString &text,
409 QHash<QString, qsizetype> *lineToCode)
411 qsizetype subtextStart = 0;
412 qsizetype subtextEnd = -1;
414 while (subtextEnd < text.size()) {
415 subtextEnd = findSubtextEnd(text, subtextStart);
416 const QString line = text.mid(subtextStart, subtextEnd - subtextStart);
417 subtextStart = subtextEnd;
419 if (lineToCode->contains(line)) {
420 codes += QChar(
static_cast<ushort>(lineToCode->value(line)));
423 lineToCode->insert(line, lines->size() - 1);
424 codes += QChar(
static_cast<ushort>(lines->size() - 1));
430QList<Diff>
Differ::merge(
const QList<Diff> &diffList)
434 QList<Diff> newDiffList;
435 for (qsizetype i = 0; i <= diffList.size(); i++) {
436 Diff diff = i < diffList.size()
441 lastDelete += diff.text;
443 lastInsert += diff.text;
445 if (!(lastDelete.isEmpty() && lastInsert.isEmpty())) {
448 const qsizetype prefixCount = commonPrefix(lastDelete, lastInsert);
450 const QString prefix = lastDelete.left(prefixCount);
451 lastDelete = lastDelete.mid(prefixCount);
452 lastInsert = lastInsert.mid(prefixCount);
454 if (!newDiffList.isEmpty()
456 newDiffList.last().text += prefix;
463 const qsizetype suffixCount = commonSuffix(lastDelete, lastInsert);
465 const QString suffix = lastDelete.right(suffixCount);
466 lastDelete = lastDelete.left(lastDelete.size() - suffixCount);
467 lastInsert = lastInsert.left(lastInsert.size() - suffixCount);
469 diff.text.prepend(suffix);
473 if (!lastDelete.isEmpty())
475 if (!lastInsert.isEmpty())
477 if (!diff.text.isEmpty())
478 newDiffList.append(diff);
482 if (!newDiffList.isEmpty()
484 newDiffList.last().text += diff.text;
486 if (!diff.text.isEmpty())
487 newDiffList.append(diff);
493 QList<Diff> squashedDiffList = squashEqualities(newDiffList);
494 if (squashedDiffList.size() != newDiffList.size())
495 return merge(squashedDiffList);
497 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)