KompareDiff2

diffmodel.cpp
1/*
2 SPDX-FileCopyrightText: 2001-2009 Otto Bruggeman <bruggie@gmail.com>
3 SPDX-FileCopyrightText: 2001-2003 John Firebaugh <jfirebaugh@kde.org>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6*/
7
8#include "diffmodel.h"
9#include "diffmodel_p.h"
10
11// lib
12#include "difference.h"
13#include "levenshteintable.h"
14#include "parserbase.h"
15#include "stringlistpair.h"
16#include <komparediff2_logging.h>
17// Std
18#include <algorithm>
19
20using namespace KompareDiff2;
21
22/** */
23DiffModel::DiffModel(const QString &source, const QString &destination)
24 : d_ptr(new DiffModelPrivate(source, destination))
25{
27
28 d->splitSourceInPathAndFileName();
29 d->splitDestinationInPathAndFileName();
30}
31
32DiffModel::DiffModel()
33 : d_ptr(new DiffModelPrivate())
34{
35}
36
37DiffModel::~DiffModel() = default;
38
39DiffModel &DiffModel::operator=(const DiffModel &model)
40{
42
43 if (&model != this) // Guard from self-assignment
44 {
45 *d = *model.d_ptr;
46 }
47
48 return *this;
49}
50
51bool DiffModel::operator<(const DiffModel &model) const
52{
53 if (localeAwareCompareSource(model) < 0)
54 return true;
55 return false;
56}
57
58int DiffModel::hunkCount() const
59{
60 Q_D(const DiffModel);
61
62 return d->hunks.count();
63}
64
65int DiffModel::differenceCount() const
66{
67 Q_D(const DiffModel);
68
69 return d->differences.count();
70}
71
72int DiffModel::appliedCount() const
73{
74 Q_D(const DiffModel);
75
76 return d->appliedCount;
77}
78
79DiffHunk *DiffModel::hunkAt(int i)
80{
82
83 return (d->hunks.at(i));
84}
85
86const Difference *DiffModel::differenceAt(int i) const
87{
88 Q_D(const DiffModel);
89
90 return (d->differences.at(i));
91}
92
93Difference *DiffModel::differenceAt(int i)
94{
96
97 return (d->differences.at(i));
98}
99
100DiffHunkList *DiffModel::hunks()
101{
102 Q_D(DiffModel);
103
104 return &d->hunks;
105}
106
107const DiffHunkList *DiffModel::hunks() const
108{
109 Q_D(const DiffModel);
110
111 return &d->hunks;
112}
113
114DifferenceList *DiffModel::differences()
115{
116 Q_D(DiffModel);
117
118 return &d->differences;
119}
120
121const DifferenceList *DiffModel::differences() const
122{
123 Q_D(const DiffModel);
124
125 return &d->differences;
126}
127
128int DiffModel::findDifference(Difference *diff) const
129{
130 Q_D(const DiffModel);
131
132 return d->differences.indexOf(diff);
133}
134
135int DiffModel::localeAwareCompareSource(const DiffModel &model) const
136{
137 Q_D(const DiffModel);
138
139 qCDebug(KOMPAREDIFF2_LOG) << "Path: " << model.d_ptr->sourcePath;
140 qCDebug(KOMPAREDIFF2_LOG) << "File: " << model.d_ptr->sourceFile;
141
142 int result = d->sourcePath.localeAwareCompare(model.d_ptr->sourcePath);
143
144 if (result == 0)
145 return d->sourceFile.localeAwareCompare(model.d_ptr->sourceFile);
146
147 return result;
148}
149
150QString DiffModel::recreateDiff() const
151{
152 Q_D(const DiffModel);
153
154 // For now we'll always return a diff in the diff format
155 QString diff;
156
157 // recreate header
158 const QChar tab = QLatin1Char('\t');
159 const QChar nl = QLatin1Char('\n');
160 diff += QStringLiteral("--- %1\t%2").arg(ParserBase::escapePath(d->source), d->sourceTimestamp);
161 if (!d->sourceRevision.isEmpty())
162 diff += tab + d->sourceRevision;
163 diff += nl;
164 diff += QStringLiteral("+++ %1\t%2").arg(ParserBase::escapePath(d->destination), d->destinationTimestamp);
165 if (!d->destinationRevision.isEmpty())
166 diff += tab + d->destinationRevision;
167 diff += nl;
168
169 // recreate body by iterating over the hunks
170 for (const DiffHunk *hunk : d->hunks) {
171 if (hunk->type() != DiffHunk::AddedByBlend) {
172 diff += hunk->recreateHunk();
173 }
174 }
175
176 return diff;
177}
178
179Difference *DiffModel::firstDifference()
180{
181 Q_D(DiffModel);
182
183 qCDebug(KOMPAREDIFF2_LOG) << "DiffModel::firstDifference()";
184 d->diffIndex = 0;
185 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
186
187 d->selectedDifference = d->differences[d->diffIndex];
188
189 return d->selectedDifference;
190}
191
192Difference *DiffModel::lastDifference()
193{
194 Q_D(DiffModel);
195
196 qCDebug(KOMPAREDIFF2_LOG) << "DiffModel::lastDifference()";
197 d->diffIndex = d->differences.count() - 1;
198 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
199
200 d->selectedDifference = d->differences[d->diffIndex];
201
202 return d->selectedDifference;
203}
204
205Difference *DiffModel::prevDifference()
206{
207 Q_D(DiffModel);
208
209 qCDebug(KOMPAREDIFF2_LOG) << "DiffModel::prevDifference()";
210 if (d->diffIndex > 0 && --d->diffIndex < d->differences.count()) {
211 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
212 d->selectedDifference = d->differences[d->diffIndex];
213 } else {
214 d->selectedDifference = nullptr;
215 d->diffIndex = 0;
216 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
217 }
218
219 return d->selectedDifference;
220}
221
222Difference *DiffModel::nextDifference()
223{
224 Q_D(DiffModel);
225
226 qCDebug(KOMPAREDIFF2_LOG) << "DiffModel::nextDifference()";
227 if (++d->diffIndex < d->differences.count()) {
228 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
229 d->selectedDifference = d->differences[d->diffIndex];
230 } else {
231 d->selectedDifference = nullptr;
232 d->diffIndex = 0; // just for safety...
233 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
234 }
235
236 return d->selectedDifference;
237}
238
239QString DiffModel::source() const
240{
241 Q_D(const DiffModel);
242
243 return d->source;
244}
245
246QString DiffModel::destination() const
247{
248 Q_D(const DiffModel);
249
250 return d->destination;
251}
252
253QString DiffModel::sourceFile() const
254{
255 Q_D(const DiffModel);
256
257 return d->sourceFile;
258}
259
260QString DiffModel::destinationFile() const
261{
262 Q_D(const DiffModel);
263
264 return d->destinationFile;
265}
266
267QString DiffModel::sourcePath() const
268{
269 Q_D(const DiffModel);
270
271 return d->sourcePath;
272}
273
274QString DiffModel::destinationPath() const
275{
276 Q_D(const DiffModel);
277
278 return d->destinationPath;
279}
280
281QString DiffModel::sourceTimestamp() const
282{
283 Q_D(const DiffModel);
284
285 return d->sourceTimestamp;
286}
287
288QString DiffModel::destinationTimestamp() const
289{
290 Q_D(const DiffModel);
291
292 return d->destinationTimestamp;
293}
294
295QString DiffModel::sourceRevision() const
296{
297 Q_D(const DiffModel);
298
299 return d->sourceRevision;
300}
301
302QString DiffModel::destinationRevision() const
303{
304 Q_D(const DiffModel);
305
306 return d->destinationRevision;
307}
308
309void DiffModel::setSourceFile(const QString &path)
310{
311 Q_D(DiffModel);
312
313 d->source = path;
314 d->splitSourceInPathAndFileName();
315}
316
317void DiffModel::setDestinationFile(const QString &path)
318{
319 Q_D(DiffModel);
320
321 d->destination = path;
322 d->splitDestinationInPathAndFileName();
323}
324
325void DiffModel::setSourceTimestamp(const QString &timestamp)
326{
327 Q_D(DiffModel);
328
329 d->sourceTimestamp = timestamp;
330}
331
332void DiffModel::setDestinationTimestamp(const QString &timestamp)
333{
334 Q_D(DiffModel);
335
336 d->destinationTimestamp = timestamp;
337}
338
339void DiffModel::setSourceRevision(const QString &revision)
340{
341 Q_D(DiffModel);
342
343 d->sourceRevision = revision;
344}
345
346void DiffModel::setDestinationRevision(const QString &revision)
347{
348 Q_D(DiffModel);
349
350 d->destinationRevision = revision;
351}
352
353bool DiffModel::isBlended() const
354{
355 Q_D(const DiffModel);
356
357 return d->blended;
358}
359
360void DiffModel::setBlended(bool blended)
361{
362 Q_D(DiffModel);
363
364 d->blended = blended;
365}
366
367void DiffModel::addHunk(DiffHunk *hunk)
368{
369 Q_D(DiffModel);
370
371 d->hunks.append(hunk);
372}
373
374void DiffModel::addDiff(Difference *diff)
375{
376 Q_D(DiffModel);
377
378 d->differences.append(diff);
379 connect(diff, &Difference::differenceApplied, this, &DiffModel::slotDifferenceApplied);
380}
381
382int DiffModel::diffIndex() const
383{
384 Q_D(const DiffModel);
385
386 return d->diffIndex;
387}
388
389void DiffModel::setDiffIndex(int diffIndex)
390{
391 Q_D(DiffModel);
392
393 d->diffIndex = diffIndex;
394}
395
396bool DiffModel::hasUnsavedChanges() const
397{
398 Q_D(const DiffModel);
399
400 return std::any_of(d->differences.constBegin(), d->differences.constEnd(), [] (const Difference* diff) {
401 return diff->isUnsaved();
402 });
403}
404
405void DiffModel::applyDifference(bool apply)
406{
407 Q_D(DiffModel);
408
409 bool appliedState = d->selectedDifference->applied();
410 if (appliedState == apply) {
411 return;
412 }
413 if (apply && !d->selectedDifference->applied())
414 ++d->appliedCount;
415 else if (!apply && d->selectedDifference->applied())
416 --d->appliedCount;
417
418 d->selectedDifference->apply(apply);
419}
420
421static int GetDifferenceDelta(Difference *diff)
422{
423 int delta = diff->destinationLineCount() - diff->sourceLineCount();
424 if (!diff->applied()) {
425 delta = -delta;
426 }
427 return delta;
428}
429
430void DiffModel::slotDifferenceApplied(Difference *diff)
431{
432 Q_D(DiffModel);
433
434 int delta = GetDifferenceDelta(diff);
435 for (Difference *current : std::as_const(d->differences)) {
436 if (current->destinationLineNumber() > diff->destinationLineNumber()) {
437 current->setTrackingDestinationLineNumber(current->trackingDestinationLineNumber() + delta);
438 }
439 }
440}
441
442void DiffModel::applyAllDifferences(bool apply)
443{
444 Q_D(DiffModel);
445
446 if (apply) {
447 d->appliedCount = d->differences.count();
448 } else {
449 d->appliedCount = 0;
450 }
451
452 int totalDelta = 0;
453
454 for (Difference *diff : std::as_const(d->differences)) {
455 diff->setTrackingDestinationLineNumber(diff->trackingDestinationLineNumber() + totalDelta);
456 const bool appliedState = diff->applied();
457 if (appliedState == apply) {
458 continue;
459 }
460 diff->applyQuietly(apply);
461 const int currentDelta = GetDifferenceDelta(diff);
462 totalDelta += currentDelta;
463 }
464}
465
466bool DiffModel::setSelectedDifference(Difference *diff)
467{
468 Q_D(DiffModel);
469
470 qCDebug(KOMPAREDIFF2_LOG) << "diff = " << diff;
471 qCDebug(KOMPAREDIFF2_LOG) << "d->selectedDifference = " << d->selectedDifference;
472
473 if (diff != d->selectedDifference) {
474 if ((d->differences.indexOf(diff)) == -1)
475 return false;
476 // Do not set d->diffIndex if it cant be found
477 d->diffIndex = d->differences.indexOf(diff);
478 qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
479 d->selectedDifference = diff;
480 }
481
482 return true;
483}
484
485QPair<QList<Difference *>, QList<Difference *>> DiffModel::linesChanged(const QStringList &oldLines, const QStringList &newLines, int editLineNumber)
486{
487 Q_D(DiffModel);
488
489 // These two will be returned as the function result
490 QList<Difference *> inserted;
491 QList<Difference *> removed;
492 if (oldLines.size() == 0 && newLines.size() == 0) {
493 return qMakePair(QList<Difference *>(), QList<Difference *>());
494 }
495 int editLineEnd = editLineNumber + oldLines.size();
496 // Find the range of differences [iterBegin, iterEnd) that should be updated
497 // TODO: assume that differences are ordered by starting line. Check that this is always the case
498 DifferenceList applied;
499 DifferenceListIterator iterBegin; // first diff ending a line before editLineNo or later
500 for (iterBegin = d->differences.begin(); iterBegin != d->differences.end(); ++iterBegin) {
501 // If the difference ends a line before the edit starts, they should be merged if this difference is applied.
502 // Also it should be merged if it starts on editLineNumber, otherwise there will be two markers for the same line
503 int lineAfterLast = (*iterBegin)->trackingDestinationLineEnd();
504 if (lineAfterLast > editLineNumber
505 || (lineAfterLast == editLineNumber && ((*iterBegin)->applied() || (*iterBegin)->trackingDestinationLineNumber() == editLineNumber))) {
506 break;
507 }
508 }
510 for (iterEnd = iterBegin; iterEnd != d->differences.end(); ++iterEnd) {
511 // If the difference starts a line after the edit ends, it should still be merged if it is applied
512 int firstLine = (*iterEnd)->trackingDestinationLineNumber();
513 if (firstLine > editLineEnd || (!(*iterEnd)->applied() && firstLine == editLineEnd)) {
514 break;
515 }
516 if ((*iterEnd)->applied()) {
517 applied.append(*iterEnd);
518 }
519 }
520
521 // Compute line numbers in source and destination to which the for diff line sequences (will be created later)
522 int sourceLineNumber;
523 int destinationLineNumber;
524 if (iterBegin == d->differences.end()) { // All existing diffs are after the change
525 destinationLineNumber = editLineNumber;
526 if (!d->differences.isEmpty()) {
527 sourceLineNumber = d->differences.last()->sourceLineEnd() - (d->differences.last()->trackingDestinationLineEnd() - editLineNumber);
528 } else {
529 sourceLineNumber = destinationLineNumber;
530 }
531 } else if (!(*iterBegin)->applied() || (*iterBegin)->trackingDestinationLineNumber() >= editLineNumber) {
532 destinationLineNumber = editLineNumber;
533 sourceLineNumber = (*iterBegin)->sourceLineNumber() - ((*iterBegin)->trackingDestinationLineNumber() - editLineNumber);
534 } else {
535 sourceLineNumber = (*iterBegin)->sourceLineNumber();
536 destinationLineNumber = (*iterBegin)->trackingDestinationLineNumber();
537 }
538
539 // Only the applied differences are of interest, unapplied can be safely removed
540 DifferenceListConstIterator appliedBegin = applied.constBegin();
541 DifferenceListConstIterator appliedEnd = applied.constEnd();
542
543 // Now create a sequence of lines for the destination file and the corresponding lines in source
544 QStringList sourceLines;
545 QStringList destinationLines;
546 DifferenceListIterator insertPosition; // where to insert the created diffs
547 if (appliedBegin == appliedEnd) {
548 destinationLines = newLines;
549 sourceLines = oldLines;
550 } else {
551 // Create the destination line sequence
552 int firstDestinationLineNumber = (*appliedBegin)->trackingDestinationLineNumber();
553 for (int lineNumber = firstDestinationLineNumber; lineNumber < editLineNumber; ++lineNumber) {
554 destinationLines.append((*appliedBegin)->destinationLineAt(lineNumber - firstDestinationLineNumber)->string());
555 }
556 for (const QString &line : newLines) {
557 destinationLines.append(line);
558 }
559 DifferenceListConstIterator appliedLast = appliedEnd;
560 --appliedLast;
561 int lastDestinationLineNumber = (*appliedLast)->trackingDestinationLineNumber();
562 for (int lineNumber = editLineEnd; lineNumber < (*appliedLast)->trackingDestinationLineEnd(); ++lineNumber) {
563 destinationLines.append((*appliedLast)->destinationLineAt(lineNumber - lastDestinationLineNumber)->string());
564 }
565
566 // Create the source line sequence
567 if ((*appliedBegin)->trackingDestinationLineNumber() >= editLineNumber) {
568 for (int i = editLineNumber; i < (*appliedBegin)->trackingDestinationLineNumber(); ++i) {
569 sourceLines.append(oldLines.at(i - editLineNumber));
570 }
571 }
572
573 for (DifferenceListConstIterator iter = appliedBegin; iter != appliedEnd;) {
574 int startPos = (*iter)->trackingDestinationLineNumber();
575 if ((*iter)->applied()) {
576 for (int i = 0; i < (*iter)->sourceLineCount(); ++i) {
577 sourceLines.append((*iter)->sourceLineAt(i)->string());
578 }
579 startPos = (*iter)->trackingDestinationLineEnd();
580 } else if (startPos < editLineNumber) {
581 startPos = editLineNumber;
582 }
583 ++iter;
584 int endPos = (iter == appliedEnd) ? editLineEnd : (*iter)->trackingDestinationLineNumber();
585 for (int i = startPos; i < endPos; ++i) {
586 sourceLines.append(oldLines.at(i - editLineNumber));
587 }
588 }
589 }
590
591 for (DifferenceListIterator iter = iterBegin; iter != iterEnd; ++iter) {
592 removed << *iter;
593 }
594 insertPosition = d->differences.erase(iterBegin, iterEnd);
595
596 // Compute the Levenshtein table for two line sequences and construct the shortest possible edit script
597 StringListPair *pair = new StringListPair(sourceLines, destinationLines);
599 table.createTable(pair);
600 table.createListsOfMarkers();
601 MarkerList sourceMarkers = pair->markerListFirst();
602 MarkerList destinationMarkers = pair->markerListSecond();
603
604 int currentSourceListLine = 0;
605 int currentDestinationListLine = 0;
606 MarkerListConstIterator sourceMarkerIter = sourceMarkers.constBegin();
607 MarkerListConstIterator destinationMarkerIter = destinationMarkers.constBegin();
608 const int terminatorLineNumber = sourceLines.size() + destinationLines.size() + 1; // A virtual offset for simpler computation - stands for infinity
609
610 // Process marker lists, converting pairs of Start-End markers into differences.
611 // Marker in source list only stands for deletion, in source and destination lists - for change, in destination list only - for insertion.
612 while (sourceMarkerIter != sourceMarkers.constEnd() || destinationMarkerIter != destinationMarkers.constEnd()) {
613 int nextSourceListLine = sourceMarkerIter != sourceMarkers.constEnd() ? (*sourceMarkerIter)->offset() : terminatorLineNumber;
614 int nextDestinationListLine = destinationMarkerIter != destinationMarkers.constEnd() ? (*destinationMarkerIter)->offset() : terminatorLineNumber;
615
616 // Advance to the nearest marker
617 int linesToSkip = qMin(nextDestinationListLine - currentDestinationListLine, nextSourceListLine - currentSourceListLine);
618 currentSourceListLine += linesToSkip;
619 currentDestinationListLine += linesToSkip;
620 Difference *diff = new Difference(sourceLineNumber + currentSourceListLine, destinationLineNumber + currentDestinationListLine);
621 if (nextSourceListLine == currentSourceListLine) {
622 DiffModelPrivate::processStartMarker(diff, sourceLines, sourceMarkerIter, currentSourceListLine, true);
623 }
624 if (nextDestinationListLine == currentDestinationListLine) {
625 DiffModelPrivate::processStartMarker(diff, destinationLines, destinationMarkerIter, currentDestinationListLine, false);
626 }
627 DiffModelPrivate::computeDiffStats(diff);
628 Q_ASSERT(diff->type() != Difference::Unchanged);
629 diff->applyQuietly(true);
630 diff->setTrackingDestinationLineNumber(diff->destinationLineNumber());
631 insertPosition = d->differences.insert(insertPosition, diff);
632 ++insertPosition;
633 inserted << diff;
634 }
635 // Update line numbers for differences that are after the edit
636 for (; insertPosition != d->differences.end(); ++insertPosition) {
637 (*insertPosition)->setTrackingDestinationLineNumber((*insertPosition)->trackingDestinationLineNumber() + (newLines.size() - oldLines.size()));
638 }
639 return qMakePair(inserted, removed);
640}
641
642#include "moc_diffmodel.cpp"
A model describing the differences between two files.
Definition diffmodel.h:31
QPair< QList< Difference * >, QList< Difference * > > linesChanged(const QStringList &oldLines, const QStringList &newLines, int editLineNumber)
oldlines - lines that were removed.
void applyQuietly(bool apply)
Apply without emitting any signals.
int trackingDestinationLineNumber() const
Destination line number that tracks applying/unapplying of other differences Essentially a line numbe...
Computes the Levenshtein distance between two sequences.
unsigned int createTable(SequencePair *sequences)
This calculates the levenshtein distance of 2 sequences.
QString path(const QString &relativePath)
KompareDiff2 namespace.
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString arg(Args &&... args) const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:13:14 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.