File indexing completed on 2024-04-21 05:44:08
0001 /* 0002 SPDX-FileCopyrightText: 2001-2009 Otto Bruggeman <bruggie@gmail.com> 0003 SPDX-FileCopyrightText: 2001-2003 John Firebaugh <jfirebaugh@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "diffmodel.h" 0009 #include "diffmodel_p.h" 0010 0011 // lib 0012 #include "difference.h" 0013 #include "levenshteintable.h" 0014 #include "parserbase.h" 0015 #include "stringlistpair.h" 0016 #include <komparediff2_logging.h> 0017 // Std 0018 #include <algorithm> 0019 0020 using namespace KompareDiff2; 0021 0022 /** */ 0023 DiffModel::DiffModel(const QString &source, const QString &destination) 0024 : d_ptr(new DiffModelPrivate(source, destination)) 0025 { 0026 Q_D(DiffModel); 0027 0028 d->splitSourceInPathAndFileName(); 0029 d->splitDestinationInPathAndFileName(); 0030 } 0031 0032 DiffModel::DiffModel() 0033 : d_ptr(new DiffModelPrivate()) 0034 { 0035 } 0036 0037 DiffModel::~DiffModel() = default; 0038 0039 DiffModel &DiffModel::operator=(const DiffModel &model) 0040 { 0041 Q_D(DiffModel); 0042 0043 if (&model != this) // Guard from self-assignment 0044 { 0045 *d = *model.d_ptr; 0046 } 0047 0048 return *this; 0049 } 0050 0051 bool DiffModel::operator<(const DiffModel &model) const 0052 { 0053 if (localeAwareCompareSource(model) < 0) 0054 return true; 0055 return false; 0056 } 0057 0058 int DiffModel::hunkCount() const 0059 { 0060 Q_D(const DiffModel); 0061 0062 return d->hunks.count(); 0063 } 0064 0065 int DiffModel::differenceCount() const 0066 { 0067 Q_D(const DiffModel); 0068 0069 return d->differences.count(); 0070 } 0071 0072 int DiffModel::appliedCount() const 0073 { 0074 Q_D(const DiffModel); 0075 0076 return d->appliedCount; 0077 } 0078 0079 DiffHunk *DiffModel::hunkAt(int i) 0080 { 0081 Q_D(DiffModel); 0082 0083 return (d->hunks.at(i)); 0084 } 0085 0086 const Difference *DiffModel::differenceAt(int i) const 0087 { 0088 Q_D(const DiffModel); 0089 0090 return (d->differences.at(i)); 0091 } 0092 0093 Difference *DiffModel::differenceAt(int i) 0094 { 0095 Q_D(DiffModel); 0096 0097 return (d->differences.at(i)); 0098 } 0099 0100 DiffHunkList *DiffModel::hunks() 0101 { 0102 Q_D(DiffModel); 0103 0104 return &d->hunks; 0105 } 0106 0107 const DiffHunkList *DiffModel::hunks() const 0108 { 0109 Q_D(const DiffModel); 0110 0111 return &d->hunks; 0112 } 0113 0114 DifferenceList *DiffModel::differences() 0115 { 0116 Q_D(DiffModel); 0117 0118 return &d->differences; 0119 } 0120 0121 const DifferenceList *DiffModel::differences() const 0122 { 0123 Q_D(const DiffModel); 0124 0125 return &d->differences; 0126 } 0127 0128 int DiffModel::findDifference(Difference *diff) const 0129 { 0130 Q_D(const DiffModel); 0131 0132 return d->differences.indexOf(diff); 0133 } 0134 0135 int DiffModel::localeAwareCompareSource(const DiffModel &model) const 0136 { 0137 Q_D(const DiffModel); 0138 0139 qCDebug(KOMPAREDIFF2_LOG) << "Path: " << model.d_ptr->sourcePath; 0140 qCDebug(KOMPAREDIFF2_LOG) << "File: " << model.d_ptr->sourceFile; 0141 0142 int result = d->sourcePath.localeAwareCompare(model.d_ptr->sourcePath); 0143 0144 if (result == 0) 0145 return d->sourceFile.localeAwareCompare(model.d_ptr->sourceFile); 0146 0147 return result; 0148 } 0149 0150 QString DiffModel::recreateDiff() const 0151 { 0152 Q_D(const DiffModel); 0153 0154 // For now we'll always return a diff in the diff format 0155 QString diff; 0156 0157 // recreate header 0158 const QChar tab = QLatin1Char('\t'); 0159 const QChar nl = QLatin1Char('\n'); 0160 diff += QStringLiteral("--- %1\t%2").arg(ParserBase::escapePath(d->source), d->sourceTimestamp); 0161 if (!d->sourceRevision.isEmpty()) 0162 diff += tab + d->sourceRevision; 0163 diff += nl; 0164 diff += QStringLiteral("+++ %1\t%2").arg(ParserBase::escapePath(d->destination), d->destinationTimestamp); 0165 if (!d->destinationRevision.isEmpty()) 0166 diff += tab + d->destinationRevision; 0167 diff += nl; 0168 0169 // recreate body by iterating over the hunks 0170 for (const DiffHunk *hunk : d->hunks) { 0171 if (hunk->type() != DiffHunk::AddedByBlend) { 0172 diff += hunk->recreateHunk(); 0173 } 0174 } 0175 0176 return diff; 0177 } 0178 0179 Difference *DiffModel::firstDifference() 0180 { 0181 Q_D(DiffModel); 0182 0183 qCDebug(KOMPAREDIFF2_LOG) << "DiffModel::firstDifference()"; 0184 d->diffIndex = 0; 0185 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex; 0186 0187 d->selectedDifference = d->differences[d->diffIndex]; 0188 0189 return d->selectedDifference; 0190 } 0191 0192 Difference *DiffModel::lastDifference() 0193 { 0194 Q_D(DiffModel); 0195 0196 qCDebug(KOMPAREDIFF2_LOG) << "DiffModel::lastDifference()"; 0197 d->diffIndex = d->differences.count() - 1; 0198 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex; 0199 0200 d->selectedDifference = d->differences[d->diffIndex]; 0201 0202 return d->selectedDifference; 0203 } 0204 0205 Difference *DiffModel::prevDifference() 0206 { 0207 Q_D(DiffModel); 0208 0209 qCDebug(KOMPAREDIFF2_LOG) << "DiffModel::prevDifference()"; 0210 if (d->diffIndex > 0 && --d->diffIndex < d->differences.count()) { 0211 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex; 0212 d->selectedDifference = d->differences[d->diffIndex]; 0213 } else { 0214 d->selectedDifference = nullptr; 0215 d->diffIndex = 0; 0216 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex; 0217 } 0218 0219 return d->selectedDifference; 0220 } 0221 0222 Difference *DiffModel::nextDifference() 0223 { 0224 Q_D(DiffModel); 0225 0226 qCDebug(KOMPAREDIFF2_LOG) << "DiffModel::nextDifference()"; 0227 if (++d->diffIndex < d->differences.count()) { 0228 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex; 0229 d->selectedDifference = d->differences[d->diffIndex]; 0230 } else { 0231 d->selectedDifference = nullptr; 0232 d->diffIndex = 0; // just for safety... 0233 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex; 0234 } 0235 0236 return d->selectedDifference; 0237 } 0238 0239 QString DiffModel::source() const 0240 { 0241 Q_D(const DiffModel); 0242 0243 return d->source; 0244 } 0245 0246 QString DiffModel::destination() const 0247 { 0248 Q_D(const DiffModel); 0249 0250 return d->destination; 0251 } 0252 0253 QString DiffModel::sourceFile() const 0254 { 0255 Q_D(const DiffModel); 0256 0257 return d->sourceFile; 0258 } 0259 0260 QString DiffModel::destinationFile() const 0261 { 0262 Q_D(const DiffModel); 0263 0264 return d->destinationFile; 0265 } 0266 0267 QString DiffModel::sourcePath() const 0268 { 0269 Q_D(const DiffModel); 0270 0271 return d->sourcePath; 0272 } 0273 0274 QString DiffModel::destinationPath() const 0275 { 0276 Q_D(const DiffModel); 0277 0278 return d->destinationPath; 0279 } 0280 0281 QString DiffModel::sourceTimestamp() const 0282 { 0283 Q_D(const DiffModel); 0284 0285 return d->sourceTimestamp; 0286 } 0287 0288 QString DiffModel::destinationTimestamp() const 0289 { 0290 Q_D(const DiffModel); 0291 0292 return d->destinationTimestamp; 0293 } 0294 0295 QString DiffModel::sourceRevision() const 0296 { 0297 Q_D(const DiffModel); 0298 0299 return d->sourceRevision; 0300 } 0301 0302 QString DiffModel::destinationRevision() const 0303 { 0304 Q_D(const DiffModel); 0305 0306 return d->destinationRevision; 0307 } 0308 0309 void DiffModel::setSourceFile(const QString &path) 0310 { 0311 Q_D(DiffModel); 0312 0313 d->source = path; 0314 d->splitSourceInPathAndFileName(); 0315 } 0316 0317 void DiffModel::setDestinationFile(const QString &path) 0318 { 0319 Q_D(DiffModel); 0320 0321 d->destination = path; 0322 d->splitDestinationInPathAndFileName(); 0323 } 0324 0325 void DiffModel::setSourceTimestamp(const QString ×tamp) 0326 { 0327 Q_D(DiffModel); 0328 0329 d->sourceTimestamp = timestamp; 0330 } 0331 0332 void DiffModel::setDestinationTimestamp(const QString ×tamp) 0333 { 0334 Q_D(DiffModel); 0335 0336 d->destinationTimestamp = timestamp; 0337 } 0338 0339 void DiffModel::setSourceRevision(const QString &revision) 0340 { 0341 Q_D(DiffModel); 0342 0343 d->sourceRevision = revision; 0344 } 0345 0346 void DiffModel::setDestinationRevision(const QString &revision) 0347 { 0348 Q_D(DiffModel); 0349 0350 d->destinationRevision = revision; 0351 } 0352 0353 bool DiffModel::isBlended() const 0354 { 0355 Q_D(const DiffModel); 0356 0357 return d->blended; 0358 } 0359 0360 void DiffModel::setBlended(bool blended) 0361 { 0362 Q_D(DiffModel); 0363 0364 d->blended = blended; 0365 } 0366 0367 void DiffModel::addHunk(DiffHunk *hunk) 0368 { 0369 Q_D(DiffModel); 0370 0371 d->hunks.append(hunk); 0372 } 0373 0374 void DiffModel::addDiff(Difference *diff) 0375 { 0376 Q_D(DiffModel); 0377 0378 d->differences.append(diff); 0379 connect(diff, &Difference::differenceApplied, this, &DiffModel::slotDifferenceApplied); 0380 } 0381 0382 int DiffModel::diffIndex() const 0383 { 0384 Q_D(const DiffModel); 0385 0386 return d->diffIndex; 0387 } 0388 0389 void DiffModel::setDiffIndex(int diffIndex) 0390 { 0391 Q_D(DiffModel); 0392 0393 d->diffIndex = diffIndex; 0394 } 0395 0396 bool DiffModel::hasUnsavedChanges() const 0397 { 0398 Q_D(const DiffModel); 0399 0400 return std::any_of(d->differences.constBegin(), d->differences.constEnd(), [] (const Difference* diff) { 0401 return diff->isUnsaved(); 0402 }); 0403 } 0404 0405 void DiffModel::applyDifference(bool apply) 0406 { 0407 Q_D(DiffModel); 0408 0409 bool appliedState = d->selectedDifference->applied(); 0410 if (appliedState == apply) { 0411 return; 0412 } 0413 if (apply && !d->selectedDifference->applied()) 0414 ++d->appliedCount; 0415 else if (!apply && d->selectedDifference->applied()) 0416 --d->appliedCount; 0417 0418 d->selectedDifference->apply(apply); 0419 } 0420 0421 static int GetDifferenceDelta(Difference *diff) 0422 { 0423 int delta = diff->destinationLineCount() - diff->sourceLineCount(); 0424 if (!diff->applied()) { 0425 delta = -delta; 0426 } 0427 return delta; 0428 } 0429 0430 void DiffModel::slotDifferenceApplied(Difference *diff) 0431 { 0432 Q_D(DiffModel); 0433 0434 int delta = GetDifferenceDelta(diff); 0435 for (Difference *current : std::as_const(d->differences)) { 0436 if (current->destinationLineNumber() > diff->destinationLineNumber()) { 0437 current->setTrackingDestinationLineNumber(current->trackingDestinationLineNumber() + delta); 0438 } 0439 } 0440 } 0441 0442 void DiffModel::applyAllDifferences(bool apply) 0443 { 0444 Q_D(DiffModel); 0445 0446 if (apply) { 0447 d->appliedCount = d->differences.count(); 0448 } else { 0449 d->appliedCount = 0; 0450 } 0451 0452 int totalDelta = 0; 0453 0454 for (Difference *diff : std::as_const(d->differences)) { 0455 diff->setTrackingDestinationLineNumber(diff->trackingDestinationLineNumber() + totalDelta); 0456 const bool appliedState = diff->applied(); 0457 if (appliedState == apply) { 0458 continue; 0459 } 0460 diff->applyQuietly(apply); 0461 const int currentDelta = GetDifferenceDelta(diff); 0462 totalDelta += currentDelta; 0463 } 0464 } 0465 0466 bool DiffModel::setSelectedDifference(Difference *diff) 0467 { 0468 Q_D(DiffModel); 0469 0470 qCDebug(KOMPAREDIFF2_LOG) << "diff = " << diff; 0471 qCDebug(KOMPAREDIFF2_LOG) << "d->selectedDifference = " << d->selectedDifference; 0472 0473 if (diff != d->selectedDifference) { 0474 if ((d->differences.indexOf(diff)) == -1) 0475 return false; 0476 // Do not set d->diffIndex if it cant be found 0477 d->diffIndex = d->differences.indexOf(diff); 0478 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex; 0479 d->selectedDifference = diff; 0480 } 0481 0482 return true; 0483 } 0484 0485 QPair<QList<Difference *>, QList<Difference *>> DiffModel::linesChanged(const QStringList &oldLines, const QStringList &newLines, int editLineNumber) 0486 { 0487 Q_D(DiffModel); 0488 0489 // These two will be returned as the function result 0490 QList<Difference *> inserted; 0491 QList<Difference *> removed; 0492 if (oldLines.size() == 0 && newLines.size() == 0) { 0493 return qMakePair(QList<Difference *>(), QList<Difference *>()); 0494 } 0495 int editLineEnd = editLineNumber + oldLines.size(); 0496 // Find the range of differences [iterBegin, iterEnd) that should be updated 0497 // TODO: assume that differences are ordered by starting line. Check that this is always the case 0498 DifferenceList applied; 0499 DifferenceListIterator iterBegin; // first diff ending a line before editLineNo or later 0500 for (iterBegin = d->differences.begin(); iterBegin != d->differences.end(); ++iterBegin) { 0501 // If the difference ends a line before the edit starts, they should be merged if this difference is applied. 0502 // Also it should be merged if it starts on editLineNumber, otherwise there will be two markers for the same line 0503 int lineAfterLast = (*iterBegin)->trackingDestinationLineEnd(); 0504 if (lineAfterLast > editLineNumber 0505 || (lineAfterLast == editLineNumber && ((*iterBegin)->applied() || (*iterBegin)->trackingDestinationLineNumber() == editLineNumber))) { 0506 break; 0507 } 0508 } 0509 DifferenceListIterator iterEnd; 0510 for (iterEnd = iterBegin; iterEnd != d->differences.end(); ++iterEnd) { 0511 // If the difference starts a line after the edit ends, it should still be merged if it is applied 0512 int firstLine = (*iterEnd)->trackingDestinationLineNumber(); 0513 if (firstLine > editLineEnd || (!(*iterEnd)->applied() && firstLine == editLineEnd)) { 0514 break; 0515 } 0516 if ((*iterEnd)->applied()) { 0517 applied.append(*iterEnd); 0518 } 0519 } 0520 0521 // Compute line numbers in source and destination to which the for diff line sequences (will be created later) 0522 int sourceLineNumber; 0523 int destinationLineNumber; 0524 if (iterBegin == d->differences.end()) { // All existing diffs are after the change 0525 destinationLineNumber = editLineNumber; 0526 if (!d->differences.isEmpty()) { 0527 sourceLineNumber = d->differences.last()->sourceLineEnd() - (d->differences.last()->trackingDestinationLineEnd() - editLineNumber); 0528 } else { 0529 sourceLineNumber = destinationLineNumber; 0530 } 0531 } else if (!(*iterBegin)->applied() || (*iterBegin)->trackingDestinationLineNumber() >= editLineNumber) { 0532 destinationLineNumber = editLineNumber; 0533 sourceLineNumber = (*iterBegin)->sourceLineNumber() - ((*iterBegin)->trackingDestinationLineNumber() - editLineNumber); 0534 } else { 0535 sourceLineNumber = (*iterBegin)->sourceLineNumber(); 0536 destinationLineNumber = (*iterBegin)->trackingDestinationLineNumber(); 0537 } 0538 0539 // Only the applied differences are of interest, unapplied can be safely removed 0540 DifferenceListConstIterator appliedBegin = applied.constBegin(); 0541 DifferenceListConstIterator appliedEnd = applied.constEnd(); 0542 0543 // Now create a sequence of lines for the destination file and the corresponding lines in source 0544 QStringList sourceLines; 0545 QStringList destinationLines; 0546 DifferenceListIterator insertPosition; // where to insert the created diffs 0547 if (appliedBegin == appliedEnd) { 0548 destinationLines = newLines; 0549 sourceLines = oldLines; 0550 } else { 0551 // Create the destination line sequence 0552 int firstDestinationLineNumber = (*appliedBegin)->trackingDestinationLineNumber(); 0553 for (int lineNumber = firstDestinationLineNumber; lineNumber < editLineNumber; ++lineNumber) { 0554 destinationLines.append((*appliedBegin)->destinationLineAt(lineNumber - firstDestinationLineNumber)->string()); 0555 } 0556 for (const QString &line : newLines) { 0557 destinationLines.append(line); 0558 } 0559 DifferenceListConstIterator appliedLast = appliedEnd; 0560 --appliedLast; 0561 int lastDestinationLineNumber = (*appliedLast)->trackingDestinationLineNumber(); 0562 for (int lineNumber = editLineEnd; lineNumber < (*appliedLast)->trackingDestinationLineEnd(); ++lineNumber) { 0563 destinationLines.append((*appliedLast)->destinationLineAt(lineNumber - lastDestinationLineNumber)->string()); 0564 } 0565 0566 // Create the source line sequence 0567 if ((*appliedBegin)->trackingDestinationLineNumber() >= editLineNumber) { 0568 for (int i = editLineNumber; i < (*appliedBegin)->trackingDestinationLineNumber(); ++i) { 0569 sourceLines.append(oldLines.at(i - editLineNumber)); 0570 } 0571 } 0572 0573 for (DifferenceListConstIterator iter = appliedBegin; iter != appliedEnd;) { 0574 int startPos = (*iter)->trackingDestinationLineNumber(); 0575 if ((*iter)->applied()) { 0576 for (int i = 0; i < (*iter)->sourceLineCount(); ++i) { 0577 sourceLines.append((*iter)->sourceLineAt(i)->string()); 0578 } 0579 startPos = (*iter)->trackingDestinationLineEnd(); 0580 } else if (startPos < editLineNumber) { 0581 startPos = editLineNumber; 0582 } 0583 ++iter; 0584 int endPos = (iter == appliedEnd) ? editLineEnd : (*iter)->trackingDestinationLineNumber(); 0585 for (int i = startPos; i < endPos; ++i) { 0586 sourceLines.append(oldLines.at(i - editLineNumber)); 0587 } 0588 } 0589 } 0590 0591 for (DifferenceListIterator iter = iterBegin; iter != iterEnd; ++iter) { 0592 removed << *iter; 0593 } 0594 insertPosition = d->differences.erase(iterBegin, iterEnd); 0595 0596 // Compute the Levenshtein table for two line sequences and construct the shortest possible edit script 0597 StringListPair *pair = new StringListPair(sourceLines, destinationLines); 0598 LevenshteinTable<StringListPair> table; 0599 table.createTable(pair); 0600 table.createListsOfMarkers(); 0601 MarkerList sourceMarkers = pair->markerListFirst(); 0602 MarkerList destinationMarkers = pair->markerListSecond(); 0603 0604 int currentSourceListLine = 0; 0605 int currentDestinationListLine = 0; 0606 MarkerListConstIterator sourceMarkerIter = sourceMarkers.constBegin(); 0607 MarkerListConstIterator destinationMarkerIter = destinationMarkers.constBegin(); 0608 const int terminatorLineNumber = sourceLines.size() + destinationLines.size() + 1; // A virtual offset for simpler computation - stands for infinity 0609 0610 // Process marker lists, converting pairs of Start-End markers into differences. 0611 // Marker in source list only stands for deletion, in source and destination lists - for change, in destination list only - for insertion. 0612 while (sourceMarkerIter != sourceMarkers.constEnd() || destinationMarkerIter != destinationMarkers.constEnd()) { 0613 int nextSourceListLine = sourceMarkerIter != sourceMarkers.constEnd() ? (*sourceMarkerIter)->offset() : terminatorLineNumber; 0614 int nextDestinationListLine = destinationMarkerIter != destinationMarkers.constEnd() ? (*destinationMarkerIter)->offset() : terminatorLineNumber; 0615 0616 // Advance to the nearest marker 0617 int linesToSkip = qMin(nextDestinationListLine - currentDestinationListLine, nextSourceListLine - currentSourceListLine); 0618 currentSourceListLine += linesToSkip; 0619 currentDestinationListLine += linesToSkip; 0620 Difference *diff = new Difference(sourceLineNumber + currentSourceListLine, destinationLineNumber + currentDestinationListLine); 0621 if (nextSourceListLine == currentSourceListLine) { 0622 DiffModelPrivate::processStartMarker(diff, sourceLines, sourceMarkerIter, currentSourceListLine, true); 0623 } 0624 if (nextDestinationListLine == currentDestinationListLine) { 0625 DiffModelPrivate::processStartMarker(diff, destinationLines, destinationMarkerIter, currentDestinationListLine, false); 0626 } 0627 DiffModelPrivate::computeDiffStats(diff); 0628 Q_ASSERT(diff->type() != Difference::Unchanged); 0629 diff->applyQuietly(true); 0630 diff->setTrackingDestinationLineNumber(diff->destinationLineNumber()); 0631 insertPosition = d->differences.insert(insertPosition, diff); 0632 ++insertPosition; 0633 inserted << diff; 0634 } 0635 // Update line numbers for differences that are after the edit 0636 for (; insertPosition != d->differences.end(); ++insertPosition) { 0637 (*insertPosition)->setTrackingDestinationLineNumber((*insertPosition)->trackingDestinationLineNumber() + (newLines.size() - oldLines.size())); 0638 } 0639 return qMakePair(inserted, removed); 0640 } 0641 0642 #include "moc_diffmodel.cpp"