diff --git a/src/Overview.cpp b/src/Overview.cpp index dd7d96b..12c31e4 100644 --- a/src/Overview.cpp +++ b/src/Overview.cpp @@ -1,314 +1,314 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "Overview.h" #include "mergeresultwindow.h" #include "options.h" #include "diff.h" #include #include #include Overview::Overview(Options* pOptions) //: QWidget( pParent, 0, Qt::WNoAutoErase ) { m_pDiff3LineList = nullptr; m_pOptions = pOptions; m_bTripleDiff = false; m_eOverviewMode = eOMNormal; m_nofLines = 1; setUpdatesEnabled(false); m_firstLine = 0; m_pageHeight = 0; setFixedWidth(20); } void Overview::init(Diff3LineList* pDiff3LineList, bool bTripleDiff) { m_pDiff3LineList = pDiff3LineList; m_bTripleDiff = bTripleDiff; m_pixmap = QPixmap(QSize(0, 0)); // make sure that a redraw happens update(); } void Overview::reset() { m_pDiff3LineList = nullptr; } void Overview::slotRedraw() { m_pixmap = QPixmap(QSize(0, 0)); // make sure that a redraw happens update(); } void Overview::setRange(int firstLine, int pageHeight) { m_firstLine = firstLine; m_pageHeight = pageHeight; update(); } void Overview::setFirstLine(int firstLine) { m_firstLine = firstLine; update(); } void Overview::setOverviewMode(e_OverviewMode eOverviewMode) { m_eOverviewMode = eOverviewMode; slotRedraw(); } Overview::e_OverviewMode Overview::getOverviewMode() { return m_eOverviewMode; } void Overview::mousePressEvent(QMouseEvent* e) { int h = height() - 1; int h1 = h * m_pageHeight / std::max(1, m_nofLines) + 3; if(h > 0) emit setLine((e->y() - h1 / 2) * m_nofLines / h); } void Overview::mouseMoveEvent(QMouseEvent* e) { mousePressEvent(e); } void Overview::setPaintingAllowed(bool bAllowPainting) { if(updatesEnabled() != bAllowPainting) { setUpdatesEnabled(bAllowPainting); if(bAllowPainting) update(); else reset(); } } void Overview::drawColumn(QPainter& p, e_OverviewMode eOverviewMode, int x, int w, int h, int nofLines) { p.setPen(Qt::black); p.drawLine(x, 0, x, h); if(nofLines == 0) return; int line = 0; int oldY = 0; int oldConflictY = -1; int wrapLineIdx = 0; Diff3LineList::const_iterator i; for(i = m_pDiff3LineList->begin(); i != m_pDiff3LineList->end();) { const Diff3Line& d3l = *i; int y = h * (line + 1) / nofLines; e_MergeDetails md; bool bConflict; bool bLineRemoved; int src; d3l.mergeOneLine(md, bConflict, bLineRemoved, src, !m_bTripleDiff); QColor c = m_pOptions->m_bgColor; bool bWhiteSpaceChange = false; //if( bConflict ) c=m_pOptions->m_colorForConflict; //else if(eOverviewMode == eOMNormal) { switch(md) { case eDefault: case eNoChange: c = m_pOptions->m_bgColor; break; case eBAdded: case eBDeleted: case eBChanged: c = bConflict ? m_pOptions->m_colorForConflict : m_pOptions->m_colorB; - bWhiteSpaceChange = d3l.bAEqB || (d3l.bWhiteLineA && d3l.bWhiteLineB); + bWhiteSpaceChange = d3l.isEqualAB() || (d3l.bWhiteLineA && d3l.bWhiteLineB); break; case eCAdded: case eCDeleted: case eCChanged: - bWhiteSpaceChange = d3l.bAEqC || (d3l.bWhiteLineA && d3l.bWhiteLineC); + bWhiteSpaceChange = d3l.isEqualAC() || (d3l.bWhiteLineA && d3l.bWhiteLineC); c = bConflict ? m_pOptions->m_colorForConflict : m_pOptions->m_colorC; break; case eBCChanged: // conflict case eBCChangedAndEqual: // possible conflict case eBCDeleted: // possible conflict case eBChanged_CDeleted: // conflict case eCChanged_BDeleted: // conflict case eBCAdded: // conflict case eBCAddedAndEqual: // possible conflict c = m_pOptions->m_colorForConflict; break; default: Q_ASSERT(true); break; } } else if(eOverviewMode == eOMAvsB) { switch(md) { case eDefault: case eNoChange: case eCAdded: case eCDeleted: case eCChanged: break; default: c = m_pOptions->m_colorForConflict; - bWhiteSpaceChange = d3l.bAEqB || (d3l.bWhiteLineA && d3l.bWhiteLineB); + bWhiteSpaceChange = d3l.isEqualAB() || (d3l.bWhiteLineA && d3l.bWhiteLineB); break; } } else if(eOverviewMode == eOMAvsC) { switch(md) { case eDefault: case eNoChange: case eBAdded: case eBDeleted: case eBChanged: break; default: c = m_pOptions->m_colorForConflict; - bWhiteSpaceChange = d3l.bAEqC || (d3l.bWhiteLineA && d3l.bWhiteLineC); + bWhiteSpaceChange = d3l.isEqualAC() || (d3l.bWhiteLineA && d3l.bWhiteLineC); break; } } else if(eOverviewMode == eOMBvsC) { switch(md) { case eDefault: case eNoChange: case eBCChangedAndEqual: case eBCDeleted: case eBCAddedAndEqual: break; default: c = m_pOptions->m_colorForConflict; - bWhiteSpaceChange = d3l.bBEqC || (d3l.bWhiteLineB && d3l.bWhiteLineC); + bWhiteSpaceChange = d3l.isEqualBC() || (d3l.bWhiteLineB && d3l.bWhiteLineC); break; } } int x2 = x; int w2 = w; if(!m_bTripleDiff) { if(d3l.getLineA() == -1 && d3l.getLineB() >= 0) { c = m_pOptions->m_colorA; x2 = w / 2; w2 = x2; } if(d3l.getLineA() >= 0 && d3l.getLineB() == -1) { c = m_pOptions->m_colorB; w2 = w / 2; } } if(!bWhiteSpaceChange || m_pOptions->m_bShowWhiteSpace) { // Make sure that lines with conflict are not overwritten. if(c == m_pOptions->m_colorForConflict) { p.fillRect(x2 + 1, oldY, w2, std::max(1, y - oldY), bWhiteSpaceChange ? QBrush(c, Qt::Dense4Pattern) : QBrush(c)); oldConflictY = oldY; } else if(c != m_pOptions->m_bgColor && oldY > oldConflictY) { p.fillRect(x2 + 1, oldY, w2, std::max(1, y - oldY), bWhiteSpaceChange ? QBrush(c, Qt::Dense4Pattern) : QBrush(c)); } } oldY = y; ++line; if(m_pOptions->m_bWordWrap) { ++wrapLineIdx; if(wrapLineIdx >= d3l.linesNeededForDisplay) { wrapLineIdx = 0; ++i; } } else { ++i; } } } void Overview::paintEvent(QPaintEvent*) { if(m_pDiff3LineList == nullptr) return; int h = height() - 1; int w = width(); if(m_pixmap.size() != size()) { if(m_pOptions->m_bWordWrap) { m_nofLines = 0; Diff3LineList::const_iterator i; for(i = m_pDiff3LineList->begin(); i != m_pDiff3LineList->end(); ++i) { m_nofLines += i->linesNeededForDisplay; } } else { m_nofLines = m_pDiff3LineList->size(); } m_pixmap = QPixmap(size()); QPainter p(&m_pixmap); p.fillRect(rect(), m_pOptions->m_bgColor); if(!m_bTripleDiff || m_eOverviewMode == eOMNormal) { drawColumn(p, eOMNormal, 0, w, h, m_nofLines); } else { drawColumn(p, eOMNormal, 0, w / 2, h, m_nofLines); drawColumn(p, m_eOverviewMode, w / 2, w / 2, h, m_nofLines); } } QPainter painter(this); painter.drawPixmap(0, 0, m_pixmap); int y1=0, h1=0; if(m_nofLines > 0) { y1 = h * m_firstLine / m_nofLines - 1; h1 = h * m_pageHeight / m_nofLines + 3; } painter.setPen(Qt::black); painter.drawRect(1, y1, w - 1, h1); } diff --git a/src/diff.cpp b/src/diff.cpp index 0c592bd..1dd25a6 100644 --- a/src/diff.cpp +++ b/src/diff.cpp @@ -1,1472 +1,1472 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "diff.h" #include "Utils.h" #include "fileaccess.h" #include "gnudiff_diff.h" #include "options.h" #include "progress.h" #include #include #include #include #include #include int LineData::width(int tabSize) const { int w = 0; int j = 0; for(int i = 0; i < size(); ++i) { if(pLine[i] == '\t') { for(j %= tabSize; j < tabSize; ++j) ++w; j = 0; } else { ++w; ++j; } } return w; } // The bStrict flag is true during the test where a nonmatching area ends. // Then the equal()-function requires that the match has more than 2 nonwhite characters. // This is to avoid matches on trivial lines (e.g. with white space only). // This choice is good for C/C++. bool equal(const LineData& l1, const LineData& l2, bool bStrict) { if(l1.getLine() == nullptr || l2.getLine() == nullptr) return false; if(bStrict && g_bIgnoreTrivialMatches) return false; // Ignore white space diff const QChar* p1 = l1.getLine(); const QChar* p1End = p1 + l1.size(); const QChar* p2 = l2.getLine(); const QChar* p2End = p2 + l2.size(); if(g_bIgnoreWhiteSpace) { int nonWhite = 0; for(;;) { while(isWhite(*p1) && p1 != p1End) ++p1; while(isWhite(*p2) && p2 != p2End) ++p2; if(p1 == p1End && p2 == p2End) { if(bStrict && g_bIgnoreTrivialMatches) { // Then equality is not enough return nonWhite > 2; } else // equality is enough return true; } else if(p1 == p1End || p2 == p2End) return false; if(*p1 != *p2) return false; ++p1; ++p2; ++nonWhite; } } else { return (l1.size() == l2.size() && memcmp(p1, p2, l1.size()) == 0); } } // First step void calcDiff3LineListUsingAB( const DiffList* pDiffListAB, Diff3LineList& d3ll) { // First make d3ll for AB (from pDiffListAB) DiffList::const_iterator i = pDiffListAB->begin(); int lineA = 0; int lineB = 0; Diff d(0, 0, 0); for(;;) { if(d.nofEquals == 0 && d.diff1 == 0 && d.diff2 == 0) { if(i != pDiffListAB->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.nofEquals > 0) { d3l.bAEqB = true; d3l.setLineA(lineA); d3l.setLineB(lineB); --d.nofEquals; ++lineA; ++lineB; } else if(d.diff1 > 0 && d.diff2 > 0) { d3l.setLineA(lineA); d3l.setLineB(lineB); --d.diff1; --d.diff2; ++lineA; ++lineB; } else if(d.diff1 > 0) { d3l.setLineA(lineA); --d.diff1; ++lineA; } else if(d.diff2 > 0) { d3l.setLineB(lineB); --d.diff2; ++lineB; } Q_ASSERT(d.nofEquals >= 0); d3ll.push_back(d3l); } } // Second step void calcDiff3LineListUsingAC( const DiffList* pDiffListAC, Diff3LineList& d3ll) { //////////////// // Now insert data from C using pDiffListAC DiffList::const_iterator i = pDiffListAC->begin(); Diff3LineList::iterator i3 = d3ll.begin(); int lineA = 0; int lineC = 0; Diff d(0, 0, 0); for(;;) { if(d.nofEquals == 0 && d.diff1 == 0 && d.diff2 == 0) { if(i != pDiffListAC->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.nofEquals > 0) { // Find the corresponding lineA while((*i3).getLineA() != lineA) ++i3; (*i3).setLineC(lineC); (*i3).bAEqC = true; - (*i3).bBEqC = (*i3).bAEqB; + (*i3).bBEqC = (*i3).isEqualAB(); --d.nofEquals; ++lineA; ++lineC; ++i3; } else if(d.diff1 > 0 && d.diff2 > 0) { d3l.setLineC(lineC); d3ll.insert(i3, d3l); --d.diff1; --d.diff2; ++lineA; ++lineC; } else if(d.diff1 > 0) { --d.diff1; ++lineA; } else if(d.diff2 > 0) { d3l.setLineC(lineC); d3ll.insert(i3, d3l); --d.diff2; ++lineC; } } } // Third step void calcDiff3LineListUsingBC( const DiffList* pDiffListBC, Diff3LineList& d3ll) { //////////////// // Now improve the position of data from C using pDiffListBC // If a line from C equals a line from A then it is in the // same Diff3Line already. // If a line from C equals a line from B but not A, this // information will be used here. DiffList::const_iterator i = pDiffListBC->begin(); Diff3LineList::iterator i3b = d3ll.begin(); Diff3LineList::iterator i3c = d3ll.begin(); int lineB = 0; int lineC = 0; Diff d(0, 0, 0); for(;;) { if(d.nofEquals == 0 && d.diff1 == 0 && d.diff2 == 0) { if(i != pDiffListBC->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.nofEquals > 0) { // Find the corresponding lineB and lineC while(i3b != d3ll.end() && (*i3b).getLineB() != lineB) ++i3b; while(i3c != d3ll.end() && (*i3c).getLineC() != lineC) ++i3c; Q_ASSERT(i3b != d3ll.end()); Q_ASSERT(i3c != d3ll.end()); if(i3b == i3c) { Q_ASSERT((*i3b).getLineC() == lineC); (*i3b).bBEqC = true; } else { // Is it possible to move this line up? // Test if no other B's are used between i3c and i3b // First test which is before: i3c or i3b ? Diff3LineList::iterator i3c1 = i3c; Diff3LineList::iterator i3b1 = i3b; while(i3c1 != i3b && i3b1 != i3c) { Q_ASSERT(i3b1 != d3ll.end() || i3c1 != d3ll.end()); if(i3c1 != d3ll.end()) ++i3c1; if(i3b1 != d3ll.end()) ++i3b1; } - if(i3c1 == i3b && !(*i3b).bAEqB) // i3c before i3b + if(i3c1 == i3b && !(*i3b).isEqualAB()) // i3c before i3b { Diff3LineList::iterator i3 = i3c; int nofDisturbingLines = 0; while(i3 != i3b && i3 != d3ll.end()) { if((*i3).getLineB() != -1) ++nofDisturbingLines; ++i3; } if(nofDisturbingLines > 0) //&& nofDisturbingLines < d.nofEquals*d.nofEquals+4 ) { Diff3LineList::iterator i3_last_equal_A = d3ll.end(); i3 = i3c; while(i3 != i3b) { - if(i3->bAEqB) + if(i3->isEqualAB()) { i3_last_equal_A = i3; } ++i3; } /* If i3_last_equal_A isn't still set to d3ll.end(), then * we've found a line in A that is equal to one in B * somewhere between i3c and i3b */ bool before_or_on_equal_line_in_A = (i3_last_equal_A != d3ll.end()); // Move the disturbing lines up, out of sight. i3 = i3c; while(i3 != i3b) { if((*i3).getLineB() != -1 || (before_or_on_equal_line_in_A && i3->getLineA() != -1)) { d3l.setLineB((*i3).getLineB()); (*i3).setLineB(-1); // Move A along if it matched B if(before_or_on_equal_line_in_A) { d3l.setLineA(i3->getLineA()); - d3l.bAEqB = i3->bAEqB; + d3l.bAEqB = i3->isEqualAB(); i3->setLineA(-1); i3->bAEqC = false; } (*i3).bAEqB = false; (*i3).bBEqC = false; d3ll.insert(i3c, d3l); } if(i3 == i3_last_equal_A) { before_or_on_equal_line_in_A = false; } ++i3; } nofDisturbingLines = 0; } if(nofDisturbingLines == 0) { // Yes, the line from B can be moved. (*i3b).setLineB(-1); // This might leave an empty line: removed later. (*i3b).bAEqB = false; (*i3b).bBEqC = false; (*i3c).setLineB(lineB); (*i3c).bBEqC = true; - (*i3c).bAEqB = (*i3c).bAEqC; + (*i3c).bAEqB = (*i3c).isEqualAC(); } } - else if(i3b1 == i3c && !(*i3c).bAEqC) + else if(i3b1 == i3c && !(*i3c).isEqualAC()) { Diff3LineList::iterator i3 = i3b; int nofDisturbingLines = 0; while(i3 != i3c && i3 != d3ll.end()) { if((*i3).getLineC() != -1) ++nofDisturbingLines; ++i3; } if(nofDisturbingLines > 0) //&& nofDisturbingLines < d.nofEquals*d.nofEquals+4 ) { Diff3LineList::iterator i3_last_equal_A = d3ll.end(); i3 = i3b; while(i3 != i3c) { - if(i3->bAEqC) + if(i3->isEqualAC()) { i3_last_equal_A = i3; } ++i3; } /* If i3_last_equal_A isn't still set to d3ll.end(), then * we've found a line in A that is equal to one in C * somewhere between i3b and i3c */ bool before_or_on_equal_line_in_A = (i3_last_equal_A != d3ll.end()); // Move the disturbing lines up. i3 = i3b; while(i3 != i3c) { if((*i3).getLineC() != -1 || (before_or_on_equal_line_in_A && i3->getLineA() != -1)) { d3l.setLineC((*i3).getLineC()); (*i3).setLineC(-1); // Move A along if it matched C if(before_or_on_equal_line_in_A) { d3l.setLineA(i3->getLineA()); - d3l.bAEqC = i3->bAEqC; + d3l.bAEqC = i3->isEqualAC(); i3->setLineA(-1); i3->bAEqB = false; } (*i3).bAEqC = false; (*i3).bBEqC = false; d3ll.insert(i3b, d3l); } if(i3 == i3_last_equal_A) { before_or_on_equal_line_in_A = false; } ++i3; } nofDisturbingLines = 0; } if(nofDisturbingLines == 0) { // Yes, the line from C can be moved. (*i3c).setLineC(-1); // This might leave an empty line: removed later. (*i3c).bAEqC = false; (*i3c).bBEqC = false; (*i3b).setLineC(lineC); (*i3b).bBEqC = true; - (*i3b).bAEqC = (*i3b).bAEqB; + (*i3b).bAEqC = (*i3b).isEqualAB(); } } } --d.nofEquals; ++lineB; ++lineC; ++i3b; ++i3c; } else if(d.diff1 > 0) { Diff3LineList::iterator i3 = i3b; while((*i3).getLineB() != lineB) ++i3; - if(i3 != i3b && !(*i3).bAEqB) + if(i3 != i3b && !(*i3).isEqualAB()) { // Take B from this line and move it up as far as possible d3l.setLineB(lineB); d3ll.insert(i3b, d3l); (*i3).setLineB(-1); } else { i3b = i3; } --d.diff1; ++lineB; ++i3b; if(d.diff2 > 0) { --d.diff2; ++lineC; } } else if(d.diff2 > 0) { --d.diff2; ++lineC; } } /* Diff3LineList::iterator it = d3ll.begin(); int li=0; for( ; it!=d3ll.end(); ++it, ++li ) { printf( "%4d %4d %4d %4d A%c=B A%c=C B%c=C\n", li, (*it).getLineA(), (*it).getLineB(), (*it).getLineC(), - (*it).bAEqB ? '=' : '!', (*it).bAEqC ? '=' : '!', (*it).bBEqC ? '=' : '!' ); + (*it).isEqualAB() ? '=' : '!', (*it).isEqualAC() ? '=' : '!', (*it).isEqualBC() ? '=' : '!' ); } printf("\n");*/ } // Test if the move would pass a barrier. Return true if not. bool ManualDiffHelpList::isValidMove(int line1, int line2, int winIdx1, int winIdx2) const { if(line1 >= 0 && line2 >= 0) { ManualDiffHelpList::const_iterator i; for(i = begin(); i != end(); ++i) { const ManualDiffHelpEntry& mdhe = *i; if(!mdhe.isValidMove(line1, line2, winIdx1, winIdx2)) return false; } } return true; // no barrier passed. } bool ManualDiffHelpEntry::isValidMove(int line1, int line2, int winIdx1, int winIdx2) const { // Barrier int l1 = winIdx1 == 1 ? lineA1 : winIdx1 == 2 ? lineB1 : lineC1; int l2 = winIdx2 == 1 ? lineA1 : winIdx2 == 2 ? lineB1 : lineC1; if(l1 >= 0 && l2 >= 0) { if((line1 >= l1 && line2 < l2) || (line1 < l1 && line2 >= l2)) return false; l1 = winIdx1 == 1 ? lineA2 : winIdx1 == 2 ? lineB2 : lineC2; l2 = winIdx2 == 1 ? lineA2 : winIdx2 == 2 ? lineB2 : lineC2; ++l1; ++l2; if((line1 >= l1 && line2 < l2) || (line1 < l1 && line2 >= l2)) return false; } return true; } static bool runDiff(const LineData* p1, LineRef size1, const LineData* p2, LineRef size2, DiffList& diffList, Options* pOptions) { ProgressProxy pp; static GnuDiff gnuDiff; // All values are initialized with zeros. pp.setCurrent(0); diffList.clear(); if(p1 == nullptr || p1[0].getLine() == nullptr || p2 == nullptr || p2[0].getLine() == nullptr || size1 == 0 || size2 == 0) { Diff d(0, 0, 0); if(p1 != nullptr && p2 != nullptr && p1[0].getLine() == nullptr && p2[0].getLine() == nullptr && size1 == size2) d.nofEquals = size1; else { d.diff1 = size1; d.diff2 = size2; } diffList.push_back(d); } else { GnuDiff::comparison comparisonInput; memset(&comparisonInput, 0, sizeof(comparisonInput)); comparisonInput.parent = nullptr; comparisonInput.file[0].buffer = p1[0].getLine(); //ptr to buffer comparisonInput.file[0].buffered = (p1[size1 - 1].getLine() - p1[0].getLine() + p1[size1 - 1].size()); // size of buffer comparisonInput.file[1].buffer = p2[0].getLine(); //ptr to buffer comparisonInput.file[1].buffered = (p2[size2 - 1].getLine() - p2[0].getLine() + p2[size2 - 1].size()); // size of buffer gnuDiff.ignore_white_space = GnuDiff::IGNORE_ALL_SPACE; // I think nobody needs anything else ... gnuDiff.bIgnoreWhiteSpace = true; gnuDiff.bIgnoreNumbers = pOptions->m_bIgnoreNumbers; gnuDiff.minimal = pOptions->m_bTryHard; gnuDiff.ignore_case = false; GnuDiff::change* script = gnuDiff.diff_2_files(&comparisonInput); LineRef equalLinesAtStart = comparisonInput.file[0].prefix_lines; LineRef currentLine1 = 0; LineRef currentLine2 = 0; GnuDiff::change* p = nullptr; for(GnuDiff::change* e = script; e; e = p) { Diff d(0, 0, 0); d.nofEquals = e->line0 - currentLine1; Q_ASSERT(d.nofEquals == e->line1 - currentLine2); d.diff1 = e->deleted; d.diff2 = e->inserted; currentLine1 += d.nofEquals + d.diff1; currentLine2 += d.nofEquals + d.diff2; diffList.push_back(d); p = e->link; free(e); } if(diffList.empty()) { Diff d(0, 0, 0); d.nofEquals = std::min(size1, size2); d.diff1 = size1 - d.nofEquals; d.diff2 = size2 - d.nofEquals; diffList.push_back(d); } else { diffList.front().nofEquals += equalLinesAtStart; currentLine1 += equalLinesAtStart; currentLine2 += equalLinesAtStart; LineRef nofEquals = std::min(size1 - currentLine1, size2 - currentLine2); if(nofEquals == 0) { diffList.back().diff1 += size1 - currentLine1; diffList.back().diff2 += size2 - currentLine2; } else { Diff d(nofEquals, size1 - currentLine1 - nofEquals, size2 - currentLine2 - nofEquals); diffList.push_back(d); } } } // Verify difflist { LineRef l1 = 0; LineRef l2 = 0; DiffList::iterator i; for(i = diffList.begin(); i != diffList.end(); ++i) { l1 += i->nofEquals + i->diff1; l2 += i->nofEquals + i->diff2; } Q_ASSERT(l1 == size1 && l2 == size2); } pp.setCurrent(1); return true; } bool ManualDiffHelpList::runDiff(const LineData* p1, LineRef size1, const LineData* p2, LineRef size2, DiffList& diffList, int winIdx1, int winIdx2, Options* pOptions) { diffList.clear(); DiffList diffList2; int l1begin = 0; int l2begin = 0; ManualDiffHelpList::const_iterator i; for(i = begin(); i != end(); ++i) { const ManualDiffHelpEntry& mdhe = *i; int l1end = mdhe.getLine1(winIdx1); int l2end = mdhe.getLine1(winIdx2); if(l1end >= 0 && l2end >= 0) { ::runDiff(p1 + l1begin, l1end - l1begin, p2 + l2begin, l2end - l2begin, diffList2, pOptions); diffList.splice(diffList.end(), diffList2); l1begin = l1end; l2begin = l2end; l1end = mdhe.getLine2(winIdx1); l2end = mdhe.getLine2(winIdx2); if(l1end >= 0 && l2end >= 0) { ++l1end; // point to line after last selected line ++l2end; ::runDiff(p1 + l1begin, l1end - l1begin, p2 + l2begin, l2end - l2begin, diffList2, pOptions); diffList.splice(diffList.end(), diffList2); l1begin = l1end; l2begin = l2end; } } } ::runDiff(p1 + l1begin, size1 - l1begin, p2 + l2begin, size2 - l2begin, diffList2, pOptions); diffList.splice(diffList.end(), diffList2); return true; } void correctManualDiffAlignment(Diff3LineList& d3ll, ManualDiffHelpList* pManualDiffHelpList) { if(pManualDiffHelpList->empty()) return; // If a line appears unaligned in comparison to the manual alignment, correct this. ManualDiffHelpList::iterator iMDHL; for(iMDHL = pManualDiffHelpList->begin(); iMDHL != pManualDiffHelpList->end(); ++iMDHL) { Diff3LineList::iterator i3 = d3ll.begin(); int missingWinIdx = 0; int alignedSum = (iMDHL->getLine1(1) < 0 ? 0 : 1) + (iMDHL->getLine1(2) < 0 ? 0 : 1) + (iMDHL->getLine1(3) < 0 ? 0 : 1); if(alignedSum == 2) { // If only A & B are aligned then let C rather be aligned with A // If only A & C are aligned then let B rather be aligned with A // If only B & C are aligned then let A rather be aligned with B missingWinIdx = iMDHL->getLine1(1) < 0 ? 1 : (iMDHL->getLine1(2) < 0 ? 2 : 3); } else if(alignedSum <= 1) { return; } // At the first aligned line, move up the two other lines into new d3ls until the second input is aligned // Then move up the third input until all three lines are aligned. int wi = 0; for(; i3 != d3ll.end(); ++i3) { for(wi = 1; wi <= 3; ++wi) { if(i3->getLineInFile(wi) >= 0 && iMDHL->firstLine(wi) == i3->getLineInFile(wi)) break; } if(wi <= 3) break; } if(wi >= 1 && wi <= 3) { // Found manual alignment for one source Diff3LineList::iterator iDest = i3; // Move lines up until the next firstLine is found. Omit wi from move and search. int wi2 = 0; for(; i3 != d3ll.end(); ++i3) { for(wi2 = 1; wi2 <= 3; ++wi2) { if(wi != wi2 && i3->getLineInFile(wi2) >= 0 && iMDHL->firstLine(wi2) == i3->getLineInFile(wi2)) break; } if(wi2 > 3) { // Not yet found // Move both others up Diff3Line d3l; // Move both up if(wi == 1) // Move B and C up { - d3l.bBEqC = i3->bBEqC; + d3l.bBEqC = i3->isEqualBC(); d3l.setLineB(i3->getLineB()); d3l.setLineC(i3->getLineC()); i3->setLineB(-1); i3->setLineC(-1); } if(wi == 2) // Move A and C up { - d3l.bAEqC = i3->bAEqC; + d3l.bAEqC = i3->isEqualAC(); d3l.setLineA(i3->getLineA()); d3l.setLineC(i3->getLineC()); i3->setLineA(-1); i3->setLineC(-1); } if(wi == 3) // Move A and B up { - d3l.bAEqB = i3->bAEqB; + d3l.bAEqB = i3->isEqualAB(); d3l.setLineA(i3->getLineA()); d3l.setLineB(i3->getLineB()); i3->setLineA(-1); i3->setLineB(-1); } i3->bAEqB = false; i3->bAEqC = false; i3->bBEqC = false; d3ll.insert(iDest, d3l); } else { // align the found line with the line we already have here if(i3 != iDest) { if(wi2 == 1) { iDest->setLineA(i3->getLineA()); i3->setLineA(-1); i3->bAEqB = false; i3->bAEqC = false; } else if(wi2 == 2) { iDest->setLineB(i3->getLineB()); i3->setLineB(-1); i3->bAEqB = false; i3->bBEqC = false; } else if(wi2 == 3) { iDest->setLineC(i3->getLineC()); i3->setLineC(-1); i3->bBEqC = false; i3->bAEqC = false; } } if(missingWinIdx != 0) { for(; i3 != d3ll.end(); ++i3) { int wi3 = missingWinIdx; if(i3->getLineInFile(wi3) >= 0) { // not found, move the line before iDest Diff3Line d3l; if(wi3 == 1) { - if(i3->bAEqB) // Stop moving lines up if one equal is found. + if(i3->isEqualAB()) // Stop moving lines up if one equal is found. break; d3l.setLineA(i3->getLineA()); i3->setLineA(-1); i3->bAEqB = false; i3->bAEqC = false; } if(wi3 == 2) { - if(i3->bAEqB) + if(i3->isEqualAB()) break; d3l.setLineB(i3->getLineB()); i3->setLineB(-1); i3->bAEqB = false; i3->bBEqC = false; } if(wi3 == 3) { - if(i3->bAEqC) + if(i3->isEqualAC()) break; d3l.setLineC(i3->getLineC()); i3->setLineC(-1); i3->bAEqC = false; i3->bBEqC = false; } d3ll.insert(iDest, d3l); } } // for(), searching for wi3 } break; } } // for(), searching for wi2 } // if, wi found } // for (iMDHL) } // Fourth step void calcDiff3LineListTrim( Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC, ManualDiffHelpList* pManualDiffHelpList) { const Diff3Line d3l_empty; d3ll.removeAll(d3l_empty); Diff3LineList::iterator i3 = d3ll.begin(); Diff3LineList::iterator i3A = d3ll.begin(); Diff3LineList::iterator i3B = d3ll.begin(); Diff3LineList::iterator i3C = d3ll.begin(); int line = 0; // diff3line counters int lineA = 0; // int lineB = 0; int lineC = 0; ManualDiffHelpList::iterator iMDHL = pManualDiffHelpList->begin(); // The iterator i3 and the variable line look ahead. // The iterators i3A, i3B, i3C and corresponding lineA, lineB and lineC stop at empty lines, if found. // If possible, then the texts from the look ahead will be moved back to the empty places. for(; i3 != d3ll.end(); ++i3, ++line) { if(iMDHL != pManualDiffHelpList->end()) { if((i3->getLineA() >= 0 && i3->getLineA() == iMDHL->getLine1(1)) || (i3->getLineB() >= 0 && i3->getLineB() == iMDHL->getLine1(2)) || (i3->getLineC() >= 0 && i3->getLineC() == iMDHL->getLine1(3))) { i3A = i3; i3B = i3; i3C = i3; lineA = line; lineB = line; lineC = line; ++iMDHL; } } - if(line > lineA && (*i3).getLineA() != -1 && (*i3A).getLineB() != -1 && (*i3A).bBEqC && + if(line > lineA && (*i3).getLineA() != -1 && (*i3A).getLineB() != -1 && (*i3A).isEqualBC() && ::equal(pldA[(*i3).getLineA()], pldB[(*i3A).getLineB()], false) && pManualDiffHelpList->isValidMove((*i3).getLineA(), (*i3A).getLineB(), 1, 2) && pManualDiffHelpList->isValidMove((*i3).getLineA(), (*i3A).getLineC(), 1, 3)) { // Empty space for A. A matches B and C in the empty line. Move it up. (*i3A).setLineA((*i3).getLineA()); (*i3A).bAEqB = true; (*i3A).bAEqC = true; (*i3).setLineA(-1); (*i3).bAEqB = false; (*i3).bAEqC = false; ++i3A; ++lineA; } - if(line > lineB && (*i3).getLineB() != -1 && (*i3B).getLineA() != -1 && (*i3B).bAEqC && + if(line > lineB && (*i3).getLineB() != -1 && (*i3B).getLineA() != -1 && (*i3B).isEqualAC() && ::equal(pldB[(*i3).getLineB()], pldA[(*i3B).getLineA()], false) && pManualDiffHelpList->isValidMove((*i3).getLineB(), (*i3B).getLineA(), 2, 1) && pManualDiffHelpList->isValidMove((*i3).getLineB(), (*i3B).getLineC(), 2, 3)) { // Empty space for B. B matches A and C in the empty line. Move it up. (*i3B).setLineB((*i3).getLineB()); (*i3B).bAEqB = true; (*i3B).bBEqC = true; (*i3).setLineB(-1); (*i3).bAEqB = false; (*i3).bBEqC = false; ++i3B; ++lineB; } - if(line > lineC && (*i3).getLineC() != -1 && (*i3C).getLineA() != -1 && (*i3C).bAEqB && + if(line > lineC && (*i3).getLineC() != -1 && (*i3C).getLineA() != -1 && (*i3C).isEqualAB() && ::equal(pldC[(*i3).getLineC()], pldA[(*i3C).getLineA()], false) && pManualDiffHelpList->isValidMove((*i3).getLineC(), (*i3C).getLineA(), 3, 1) && pManualDiffHelpList->isValidMove((*i3).getLineC(), (*i3C).getLineB(), 3, 2)) { // Empty space for C. C matches A and B in the empty line. Move it up. (*i3C).setLineC((*i3).getLineC()); (*i3C).bAEqC = true; (*i3C).bBEqC = true; (*i3).setLineC(-1); (*i3).bAEqC = false; (*i3).bBEqC = false; ++i3C; ++lineC; } - if(line > lineA && (*i3).getLineA() != -1 && !(*i3).bAEqB && !(*i3).bAEqC && + if(line > lineA && (*i3).getLineA() != -1 && !(*i3).isEqualAB() && !(*i3).isEqualAC() && pManualDiffHelpList->isValidMove((*i3).getLineA(), (*i3A).getLineB(), 1, 2) && pManualDiffHelpList->isValidMove((*i3).getLineA(), (*i3A).getLineC(), 1, 3)) { // Empty space for A. A doesn't match B or C. Move it up. (*i3A).setLineA((*i3).getLineA()); (*i3).setLineA(-1); if(i3A->getLineB() != -1 && ::equal(pldA[i3A->getLineA()], pldB[i3A->getLineB()], false)) { i3A->bAEqB = true; } - if((i3A->bAEqB && i3A->bBEqC) || + if((i3A->isEqualAB() && i3A->isEqualBC()) || (i3A->getLineC() != -1 && ::equal(pldA[i3A->getLineA()], pldC[i3A->getLineC()], false))) { i3A->bAEqC = true; } ++i3A; ++lineA; } - if(line > lineB && (*i3).getLineB() != -1 && !(*i3).bAEqB && !(*i3).bBEqC && + if(line > lineB && (*i3).getLineB() != -1 && !(*i3).isEqualAB() && !(*i3).isEqualBC() && pManualDiffHelpList->isValidMove((*i3).getLineB(), (*i3B).getLineA(), 2, 1) && pManualDiffHelpList->isValidMove((*i3).getLineB(), (*i3B).getLineC(), 2, 3)) { // Empty space for B. B matches neither A nor C. Move B up. (*i3B).setLineB((*i3).getLineB()); (*i3).setLineB(-1); if(i3B->getLineA() != -1 && ::equal(pldA[i3B->getLineA()], pldB[i3B->getLineB()], false)) { i3B->bAEqB = true; } - if((i3B->bAEqB && i3B->bAEqC) || + if((i3B->isEqualAB() && i3B->isEqualAC()) || (i3B->getLineC() != -1 && ::equal(pldB[i3B->getLineB()], pldC[i3B->getLineC()], false))) { i3B->bBEqC = true; } ++i3B; ++lineB; } - if(line > lineC && (*i3).getLineC() != -1 && !(*i3).bAEqC && !(*i3).bBEqC && + if(line > lineC && (*i3).getLineC() != -1 && !(*i3).isEqualAC() && !(*i3).isEqualBC() && pManualDiffHelpList->isValidMove( (*i3).getLineC(), (*i3C).getLineA(), 3, 1) && pManualDiffHelpList->isValidMove( (*i3).getLineC(), (*i3C).getLineB(), 3, 2)) { // Empty space for C. C matches neither A nor B. Move C up. (*i3C).setLineC((*i3).getLineC()); (*i3).setLineC(-1); if(i3C->getLineA() != -1 && ::equal(pldA[i3C->getLineA()], pldC[i3C->getLineC()], false)) { i3C->bAEqC = true; } - if((i3C->bAEqC && i3C->bAEqB) || + if((i3C->isEqualAC() && i3C->isEqualAB()) || (i3C->getLineB() != -1 && ::equal(pldB[i3C->getLineB()], pldC[i3C->getLineC()], false))) { i3C->bBEqC = true; } ++i3C; ++lineC; } - if(line > lineA && line > lineB && (*i3).getLineA() != -1 && (*i3).bAEqB && !(*i3).bAEqC) + if(line > lineA && line > lineB && (*i3).getLineA() != -1 && (*i3).isEqualAB() && !(*i3).isEqualAC()) { // Empty space for A and B. A matches B, but not C. Move A & B up. Diff3LineList::iterator i = lineA > lineB ? i3A : i3B; int l = lineA > lineB ? lineA : lineB; if(pManualDiffHelpList->isValidMove( i->getLineC(), (*i3).getLineA(), 3, 1) && pManualDiffHelpList->isValidMove( i->getLineC(), (*i3).getLineB(), 3, 2)) { (*i).setLineA((*i3).getLineA()); (*i).setLineB((*i3).getLineB()); (*i).bAEqB = true; if(i->getLineC() != -1 && ::equal(pldA[i->getLineA()], pldC[i->getLineC()], false)) { (*i).bAEqC = true; (*i).bBEqC = true; } (*i3).setLineA(-1); (*i3).setLineB(-1); (*i3).bAEqB = false; i3A = i; i3B = i; ++i3A; ++i3B; lineA = l + 1; lineB = l + 1; } } - else if(line > lineA && line > lineC && (*i3).getLineA() != -1 && (*i3).bAEqC && !(*i3).bAEqB) + else if(line > lineA && line > lineC && (*i3).getLineA() != -1 && (*i3).isEqualAC() && !(*i3).isEqualAB()) { // Empty space for A and C. A matches C, but not B. Move A & C up. Diff3LineList::iterator i = lineA > lineC ? i3A : i3C; int l = lineA > lineC ? lineA : lineC; if(pManualDiffHelpList->isValidMove(i->getLineB(), (*i3).getLineA(), 2, 1) && pManualDiffHelpList->isValidMove(i->getLineB(), (*i3).getLineC(), 2, 3)) { (*i).setLineA((*i3).getLineA()); (*i).setLineC((*i3).getLineC()); (*i).bAEqC = true; if(i->getLineB() != -1 && ::equal(pldA[i->getLineA()], pldB[i->getLineB()], false)) { (*i).bAEqB = true; (*i).bBEqC = true; } (*i3).setLineA(-1); (*i3).setLineC(-1); (*i3).bAEqC = false; i3A = i; i3C = i; ++i3A; ++i3C; lineA = l + 1; lineC = l + 1; } } - else if(line > lineB && line > lineC && (*i3).getLineB() != -1 && (*i3).bBEqC && !(*i3).bAEqC) + else if(line > lineB && line > lineC && (*i3).getLineB() != -1 && (*i3).isEqualBC() && !(*i3).isEqualAC()) { // Empty space for B and C. B matches C, but not A. Move B & C up. Diff3LineList::iterator i = lineB > lineC ? i3B : i3C; int l = lineB > lineC ? lineB : lineC; if(pManualDiffHelpList->isValidMove( i->getLineA(), (*i3).getLineB(), 1, 2) && pManualDiffHelpList->isValidMove( i->getLineA(), (*i3).getLineC(), 1, 3)) { (*i).setLineB((*i3).getLineB()); (*i).setLineC((*i3).getLineC()); (*i).bBEqC = true; if(i->getLineA() != -1 && ::equal(pldA[i->getLineA()], pldB[i->getLineB()], false)) { (*i).bAEqB = true; (*i).bAEqC = true; } (*i3).setLineB(-1); (*i3).setLineC(-1); (*i3).bBEqC = false; i3B = i; i3C = i; ++i3B; ++i3C; lineB = l + 1; lineC = l + 1; } } if((*i3).getLineA() != -1) { lineA = line + 1; i3A = i3; ++i3A; } if((*i3).getLineB() != -1) { lineB = line + 1; i3B = i3; ++i3B; } if((*i3).getLineC() != -1) { lineC = line + 1; i3C = i3; ++i3C; } } d3ll.removeAll(d3l_empty); /* Diff3LineList::iterator it = d3ll.begin(); int li=0; for( ; it!=d3ll.end(); ++it, ++li ) { printf( "%4d %4d %4d %4d A%c=B A%c=C B%c=C\n", li, (*it).getLineA(), (*it).getLineB(), (*it).getLineC(), - (*it).bAEqB ? '=' : '!', (*it).bAEqC ? '=' : '!', (*it).bBEqC ? '=' : '!' ); + (*it).isEqualAB() ? '=' : '!', (*it).isEqualAC() ? '=' : '!', (*it).isEqualBC() ? '=' : '!' ); } */ } void DiffBufferInfo::init(Diff3LineList* pD3ll, const Diff3LineVector* pD3lv, const LineData* pldA, LineRef sizeA, const LineData* pldB, LineRef sizeB, const LineData* pldC, LineRef sizeC) { m_pDiff3LineList = pD3ll; m_pDiff3LineVector = pD3lv; m_pLineDataA = pldA; m_pLineDataB = pldB; m_pLineDataC = pldC; m_sizeA = sizeA; m_sizeB = sizeB; m_sizeC = sizeC; Diff3LineList::iterator i3 = pD3ll->begin(); for(; i3 != pD3ll->end(); ++i3) { i3->m_pDiffBufferInfo = this; } } void Diff3LineList::calcWhiteDiff3Lines( const LineData* pldA, const LineData* pldB, const LineData* pldC) { Diff3LineList::iterator i3; for(i3=begin(); i3 != end(); ++i3) { i3->bWhiteLineA = ((*i3).getLineA() == -1 || pldA == nullptr || pldA[(*i3).getLineA()].whiteLine() || pldA[(*i3).getLineA()].isPureComment()); i3->bWhiteLineB = ((*i3).getLineB() == -1 || pldB == nullptr || pldB[(*i3).getLineB()].whiteLine() || pldB[(*i3).getLineB()].isPureComment()); i3->bWhiteLineC = ((*i3).getLineC() == -1 || pldC == nullptr || pldC[(*i3).getLineC()].whiteLine() || pldC[(*i3).getLineC()].isPureComment()); } } inline bool equal(QChar c1, QChar c2, bool /*bStrict*/) { // If bStrict then white space doesn't match //if ( bStrict && ( c1==' ' || c1=='\t' ) ) // return false; return c1 == c2; } // My own diff-invention: template void calcDiff(const T* p1, LineRef size1, const T* p2, LineRef size2, DiffList& diffList, int match, int maxSearchRange) { diffList.clear(); const T* p1start = p1; const T* p2start = p2; const T* p1end = p1 + size1; const T* p2end = p2 + size2; for(;;) { int nofEquals = 0; while(p1 != p1end && p2 != p2end && equal(*p1, *p2, false)) { ++p1; ++p2; ++nofEquals; } bool bBestValid = false; int bestI1 = 0; int bestI2 = 0; int i1 = 0; int i2 = 0; for(i1 = 0;; ++i1) { if(&p1[i1] == p1end || (bBestValid && i1 >= bestI1 + bestI2)) { break; } for(i2 = 0; i2 < maxSearchRange; ++i2) { if(&p2[i2] == p2end || (bBestValid && i1 + i2 >= bestI1 + bestI2)) { break; } else if(equal(p2[i2], p1[i1], true) && (match == 1 || abs(i1 - i2) < 3 || (&p2[i2 + 1] == p2end && &p1[i1 + 1] == p1end) || (&p2[i2 + 1] != p2end && &p1[i1 + 1] != p1end && equal(p2[i2 + 1], p1[i1 + 1], false)))) { if(i1 + i2 < bestI1 + bestI2 || !bBestValid) { bestI1 = i1; bestI2 = i2; bBestValid = true; break; } } } } // The match was found using the strict search. Go back if there are non-strict // matches. while(bestI1 >= 1 && bestI2 >= 1 && equal(p1[bestI1 - 1], p2[bestI2 - 1], false)) { --bestI1; --bestI2; } bool bEndReached = false; if(bBestValid) { // continue somehow Diff d(nofEquals, bestI1, bestI2); diffList.push_back(d); p1 += bestI1; p2 += bestI2; } else { // Nothing else to match. Diff d(nofEquals, p1end - p1, p2end - p2); diffList.push_back(d); bEndReached = true; //break; } // Sometimes the algorithm that chooses the first match unfortunately chooses // a match where later actually equal parts don't match anymore. // A different match could be achieved, if we start at the end. // Do it, if it would be a better match. int nofUnmatched = 0; const T* pu1 = p1 - 1; const T* pu2 = p2 - 1; while(pu1 >= p1start && pu2 >= p2start && equal(*pu1, *pu2, false)) { ++nofUnmatched; --pu1; --pu2; } Diff d = diffList.back(); if(nofUnmatched > 0) { // We want to go backwards the nofUnmatched elements and redo // the matching d = diffList.back(); Diff origBack = d; diffList.pop_back(); while(nofUnmatched > 0) { if(d.diff1 > 0 && d.diff2 > 0) { --d.diff1; --d.diff2; --nofUnmatched; } else if(d.nofEquals > 0) { --d.nofEquals; --nofUnmatched; } if(d.nofEquals == 0 && (d.diff1 == 0 || d.diff2 == 0) && nofUnmatched > 0) { if(diffList.empty()) break; d.nofEquals += diffList.back().nofEquals; d.diff1 += diffList.back().diff1; d.diff2 += diffList.back().diff2; diffList.pop_back(); bEndReached = false; } } if(bEndReached) diffList.push_back(origBack); else { p1 = pu1 + 1 + nofUnmatched; p2 = pu2 + 1 + nofUnmatched; diffList.push_back(d); } } if(bEndReached) break; } // Verify difflist { LineRef l1 = 0; LineRef l2 = 0; DiffList::iterator i; for(i = diffList.begin(); i != diffList.end(); ++i) { l1 += i->nofEquals + i->diff1; l2 += i->nofEquals + i->diff2; } Q_ASSERT(l1 == size1 && l2 == size2); } } bool Diff3Line::fineDiff(const int selector, const LineData* v1, const LineData* v2) { bool bTextsTotalEqual = true; LineRef k1 = 0; LineRef k2 = 0; int maxSearchLength = 500; Q_ASSERT(selector == 1 || selector == 2 || selector == 3); if(selector == 1) { k1 = getLineA(); k2 = getLineB(); } else if(selector == 2) { k1 = getLineB(); k2 = getLineC(); } else if(selector == 3) { k1 = getLineC(); k2 = getLineA(); } if((k1 == -1 && k2 != -1) || (k1 != -1 && k2 == -1)) bTextsTotalEqual = false; if(k1 != -1 && k2 != -1) { if(v1[k1].size() != v2[k2].size() || memcmp(v1[k1].getLine(), v2[k2].getLine(), v1[k1].size() << 1) != 0) { bTextsTotalEqual = false; DiffList* pDiffList = new DiffList; calcDiff(v1[k1].getLine(), v1[k1].size(), v2[k2].getLine(), v2[k2].size(), *pDiffList, 2, maxSearchLength); // Optimize the diff list. DiffList::iterator dli; bool bUsefulFineDiff = false; for(dli = pDiffList->begin(); dli != pDiffList->end(); ++dli) { if(dli->nofEquals >= 4) { bUsefulFineDiff = true; break; } } for(dli = pDiffList->begin(); dli != pDiffList->end(); ++dli) { if(dli->nofEquals < 4 && (dli->diff1 > 0 || dli->diff2 > 0) && !(bUsefulFineDiff && dli == pDiffList->begin())) { dli->diff1 += dli->nofEquals; dli->diff2 += dli->nofEquals; dli->nofEquals = 0; } } setFineDiff(selector, pDiffList); } if((v1[k1].isPureComment() || v1[k1].whiteLine()) && (v2[k2].isPureComment() || v2[k2].whiteLine())) { if(selector == 1) { bAEqB = true; } else if(selector == 2) { bBEqC = true; } else if(selector == 3) { bAEqC = true; } } } return bTextsTotalEqual; } void Diff3Line::getLineInfo(const int winIdx, const bool isTriple, int& lineIdx, DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values int& changed, int& changed2) const { changed = 0; changed2 = 0; - bool bAEqualB = this->bAEqB || (bWhiteLineA && bWhiteLineB); - bool bAEqualC = this->bAEqC || (bWhiteLineA && bWhiteLineC); - bool bBEqualC = this->bBEqC || (bWhiteLineB && bWhiteLineC); + bool bAEqualB = this->isEqualAB() || (bWhiteLineA && bWhiteLineB); + bool bAEqualC = this->isEqualAC() || (bWhiteLineA && bWhiteLineC); + bool bBEqualC = this->isEqualBC() || (bWhiteLineB && bWhiteLineC); Q_ASSERT(winIdx >= 1 && winIdx <= 3); if(winIdx == 1) { lineIdx = getLineA(); pFineDiff1 = pFineAB; pFineDiff2 = pFineCA; changed |= ((getLineB() == -1) != (lineIdx == -1) ? 1 : 0) + ((getLineC() == -1) != (lineIdx == -1) && isTriple ? 2 : 0); changed2 |= (bAEqualB ? 0 : 1) + (bAEqualC || !isTriple ? 0 : 2); } else if(winIdx == 2) { lineIdx = getLineB(); pFineDiff1 = pFineBC; pFineDiff2 = pFineAB; changed |= ((getLineC() == -1) != (lineIdx == -1) && isTriple ? 1 : 0) + ((getLineA() == -1) != (lineIdx == -1) ? 2 : 0); changed2 |= (bBEqualC || !isTriple ? 0 : 1) + (bAEqualB ? 0 : 2); } else if(winIdx == 3) { lineIdx = getLineC(); pFineDiff1 = pFineCA; pFineDiff2 = pFineBC; changed |= ((getLineA() == -1) != (lineIdx == -1) ? 1 : 0) + ((getLineB() == -1) != (lineIdx == -1) ? 2 : 0); changed2 |= (bAEqualC ? 0 : 1) + (bBEqualC ? 0 : 2); } } bool Diff3LineList::fineDiff(const int selector, const LineData* v1, const LineData* v2) { // Finetuning: Diff each line with deltas ProgressProxy pp; Diff3LineList::iterator i; bool bTextsTotalEqual = true; int listSize = size(); pp.setMaxNofSteps(listSize); int listIdx = 0; for(i = begin(); i != end(); ++i) { bTextsTotalEqual = i->fineDiff(selector, v1, v2); ++listIdx; pp.step(); } return bTextsTotalEqual; } // Convert the list to a vector of pointers void Diff3LineList::calcDiff3LineVector(Diff3LineVector& d3lv) { d3lv.resize(size()); Diff3LineList::iterator i; int j = 0; for(i = begin(); i != end(); ++i, ++j) { d3lv[j] = &(*i); } Q_ASSERT(j == (int)d3lv.size()); } diff --git a/src/diff.h b/src/diff.h index 5938d07..cd8ffa9 100644 --- a/src/diff.h +++ b/src/diff.h @@ -1,461 +1,465 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef DIFF_H #define DIFF_H #include #include #include "common.h" #include "fileaccess.h" #include "options.h" #include "gnudiff_diff.h" #include "SourceData.h" enum e_MergeDetails { eDefault, eNoChange, eBChanged, eCChanged, eBCChanged, // conflict eBCChangedAndEqual, // possible conflict eBDeleted, eCDeleted, eBCDeleted, // possible conflict eBChanged_CDeleted, // conflict eCChanged_BDeleted, // conflict eBAdded, eCAdded, eBCAdded, // conflict eBCAddedAndEqual // possible conflict }; // Each range with matching elements is followed by a range with differences on either side. // Then again range of matching elements should follow. class Diff { public: LineRef nofEquals; qint64 diff1; qint64 diff2; Diff(LineRef eq, qint64 d1, qint64 d2) { nofEquals = eq; diff1 = d1; diff2 = d2; } }; typedef std::list DiffList; class LineData { private: const QChar* pLine = nullptr; const QChar* pFirstNonWhiteChar = nullptr; int mSize = 0; bool bContainsPureComment = false; public: inline int size() const { return mSize; } inline void setSize(const int newSize) { mSize = newSize; } inline void setFirstNonWhiteChar(const QChar* firstNonWhiteChar) { pFirstNonWhiteChar = firstNonWhiteChar;} inline const QChar* getFirstNonWhiteChar() const { return pFirstNonWhiteChar; } inline const QChar* getLine() const { return pLine; } inline void setLine(const QChar* line) { pLine = line;} int width(int tabSize) const; // Calcs width considering tabs. //int occurrences; bool whiteLine() const { return pFirstNonWhiteChar - pLine == mSize; } bool isPureComment() const { return bContainsPureComment; } void setPureComment(const bool bPureComment) { bContainsPureComment = bPureComment; } }; class Diff3LineList; class Diff3LineVector; class DiffBufferInfo { public: const LineData* m_pLineDataA; const LineData* m_pLineDataB; const LineData* m_pLineDataC; LineRef m_sizeA; LineRef m_sizeB; LineRef m_sizeC; const Diff3LineList* m_pDiff3LineList; const Diff3LineVector* m_pDiff3LineVector; void init(Diff3LineList* d3ll, const Diff3LineVector* d3lv, const LineData* pldA, LineRef sizeA, const LineData* pldB, LineRef sizeB, const LineData* pldC, LineRef sizeC); }; class Diff3Line { private: LineRef lineA = -1; LineRef lineB = -1; LineRef lineC = -1; public: bool bAEqC = false; // These are true if equal or only white-space changes exist. bool bBEqC = false; bool bAEqB = false; bool bWhiteLineA = false; bool bWhiteLineB = false; bool bWhiteLineC = false; DiffList* pFineAB = nullptr; // These are 0 only if completely equal or if either source doesn't exist. DiffList* pFineBC = nullptr; DiffList* pFineCA = nullptr; int linesNeededForDisplay = 1; // Due to wordwrap int sumLinesNeededForDisplay = 0; // For fast conversion to m_diff3WrapLineVector DiffBufferInfo* m_pDiffBufferInfo = nullptr; // For convenience ~Diff3Line() { if(pFineAB != nullptr) delete pFineAB; if(pFineBC != nullptr) delete pFineBC; if(pFineCA != nullptr) delete pFineCA; pFineAB = nullptr; pFineBC = nullptr; pFineCA = nullptr; } LineRef getLineA() const { return lineA; } LineRef getLineB() const { return lineB; } LineRef getLineC() const { return lineC; } - void setLineA(const LineRef& line) { lineA = line; } - void setLineB(const LineRef& line) { lineB = line; } - void setLineC(const LineRef& line) { lineC = line; } + inline void setLineA(const LineRef& line) { lineA = line; } + inline void setLineB(const LineRef& line) { lineB = line; } + inline void setLineC(const LineRef& line) { lineC = line; } + + inline bool isEqualAB() const { return bAEqB; } + inline bool isEqualAC() const { return bAEqC; } + inline bool isEqualBC() const { return bBEqC; } bool operator==(const Diff3Line& d3l) const { return lineA == d3l.lineA && lineB == d3l.lineB && lineC == d3l.lineC && bAEqB == d3l.bAEqB && bAEqC == d3l.bAEqC && bBEqC == d3l.bBEqC; } const LineData* getLineData(int src) const { Q_ASSERT(m_pDiffBufferInfo != nullptr); if(src == 1 && lineA >= 0) return &m_pDiffBufferInfo->m_pLineDataA[lineA]; if(src == 2 && lineB >= 0) return &m_pDiffBufferInfo->m_pLineDataB[lineB]; if(src == 3 && lineC >= 0) return &m_pDiffBufferInfo->m_pLineDataC[lineC]; return nullptr; } QString getString(int src) const { const LineData* pld = getLineData(src); if(pld) return QString(pld->getLine(), pld->size()); else return QString(); } LineRef getLineInFile(int src) const { if(src == 1) return lineA; if(src == 2) return lineB; if(src == 3) return lineC; return -1; } bool fineDiff(const int selector, const LineData* v1, const LineData* v2); void mergeOneLine(e_MergeDetails& mergeDetails, bool& bConflict, bool& bLineRemoved, int& src, bool bTwoInputs) const; void getLineInfo(const int winIdx, const bool isTriple, int& lineIdx, DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values int& changed, int& changed2) const; private: void setFineDiff(const int selector, DiffList* pDiffList) { Q_ASSERT(selector == 1 || selector == 2 || selector == 3); if(selector == 1) { if(pFineAB != nullptr) delete pFineAB; pFineAB = pDiffList; } else if(selector == 2) { if(pFineBC != nullptr) delete pFineBC; pFineBC = pDiffList; } else if(selector == 3) { if(pFineCA) delete pFineCA; pFineCA = pDiffList; } } }; class Diff3LineList : public QList { public: bool fineDiff(const int selector, const LineData* v1, const LineData* v2); void calcDiff3LineVector(Diff3LineVector& d3lv); void calcWhiteDiff3Lines(const LineData* pldA, const LineData* pldB, const LineData* pldC); }; class Diff3LineVector : public QVector { }; class Diff3WrapLine { public: Diff3Line* pD3L; int diff3LineIndex; int wrapLineOffset; int wrapLineLength; }; typedef QVector Diff3WrapLineVector; class TotalDiffStatus { public: inline void reset() { bBinaryAEqC = false; bBinaryBEqC = false; bBinaryAEqB = false; bTextAEqC = false; bTextBEqC = false; bTextAEqB = false; nofUnsolvedConflicts = 0; nofSolvedConflicts = 0; nofWhitespaceConflicts = 0; } inline int getUnsolvedConflicts() const { return nofUnsolvedConflicts; } inline void setUnsolvedConflicts(const int unsolved) { nofUnsolvedConflicts = unsolved; } inline int getSolvedConflicts() const { return nofSolvedConflicts; } inline void setSolvedConflicts(const int solved) { nofSolvedConflicts = solved; } inline int getWhitespaceConflicts() const { return nofWhitespaceConflicts; } inline void setWhitespaceConflicts(const int wintespace) { nofWhitespaceConflicts = wintespace; } inline int getNonWhitespaceConflicts() { return getUnsolvedConflicts() + getSolvedConflicts() - getWhitespaceConflicts(); } bool isBinaryEqualAC() const { return bBinaryAEqC; } bool isBinaryEqualBC() const { return bBinaryBEqC; } bool isBinaryEqualAB() const { return bBinaryAEqB; } bool bBinaryAEqC = false; bool bBinaryBEqC = false; bool bBinaryAEqB = false; bool bTextAEqC = false; bool bTextBEqC = false; bool bTextAEqB = false; private: int nofUnsolvedConflicts = 0; int nofSolvedConflicts = 0; int nofWhitespaceConflicts = 0; }; class ManualDiffHelpList; // A list of corresponding ranges // Three corresponding ranges. (Minimum size of a valid range is one line.) class ManualDiffHelpEntry { private: LineRef lineA1 = -1; LineRef lineA2 = -1; LineRef lineB1 = -1; LineRef lineB2 = -1; LineRef lineC1 = -1; LineRef lineC2 = -1; public: LineRef& firstLine(int winIdx) { return winIdx == 1 ? lineA1 : (winIdx == 2 ? lineB1 : lineC1); } LineRef& lastLine(int winIdx) { return winIdx == 1 ? lineA2 : (winIdx == 2 ? lineB2 : lineC2); } bool isLineInRange(LineRef line, int winIdx) { return line >= 0 && line >= firstLine(winIdx) && line <= lastLine(winIdx); } bool operator==(const ManualDiffHelpEntry& r) const { return lineA1 == r.lineA1 && lineB1 == r.lineB1 && lineC1 == r.lineC1 && lineA2 == r.lineA2 && lineB2 == r.lineB2 && lineC2 == r.lineC2; } int calcManualDiffFirstDiff3LineIdx(const Diff3LineVector& d3lv); void getRangeForUI(const int winIdx, int *rangeLine1, int *rangeLine2) const { if(winIdx == 1) { *rangeLine1 = lineA1; *rangeLine2 = lineA2; } if(winIdx == 2) { *rangeLine1 = lineB1; *rangeLine2 = lineB2; } if(winIdx == 3) { *rangeLine1 = lineC1; *rangeLine2 = lineC2; } } inline int getLine1(const int winIdx) const { return winIdx == 1 ? lineA1 : winIdx == 2 ? lineB1 : lineC1;} inline int getLine2(const int winIdx) const { return winIdx == 1 ? lineA2 : winIdx == 2 ? lineB2 : lineC2;} bool isValidMove(int line1, int line2, int winIdx1, int winIdx2) const; }; // A list of corresponding ranges class ManualDiffHelpList: public std::list { public: bool isValidMove(int line1, int line2, int winIdx1, int winIdx2) const; void insertEntry(int winIdx, LineRef firstLine, LineRef lastLine); bool runDiff(const LineData* p1, LineRef size1, const LineData* p2, LineRef size2, DiffList& diffList, int winIdx1, int winIdx2, Options* pOptions); }; void calcDiff3LineListUsingAB( const DiffList* pDiffListAB, Diff3LineList& d3ll); void calcDiff3LineListUsingAC( const DiffList* pDiffListAC, Diff3LineList& d3ll); void calcDiff3LineListUsingBC( const DiffList* pDiffListBC, Diff3LineList& d3ll); void correctManualDiffAlignment(Diff3LineList& d3ll, ManualDiffHelpList* pManualDiffHelpList); void calcDiff3LineListTrim(Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC, ManualDiffHelpList* pManualDiffHelpList); // Helper class that swaps left and right for some commands. class MyPainter : public QPainter { int m_factor; int m_xOffset; int m_fontWidth; public: MyPainter(QPaintDevice* pd, bool bRTL, int width, int fontWidth) : QPainter(pd) { if(bRTL) { m_fontWidth = fontWidth; m_factor = -1; m_xOffset = width - 1; } else { m_fontWidth = 0; m_factor = 1; m_xOffset = 0; } } void fillRect(int x, int y, int w, int h, const QBrush& b) { if(m_factor == 1) QPainter::fillRect(m_xOffset + x, y, w, h, b); else QPainter::fillRect(m_xOffset - x - w, y, w, h, b); } void drawText(int x, int y, const QString& s, bool bAdapt = false) { Qt::LayoutDirection ld = (m_factor == 1 || !bAdapt) ? Qt::LeftToRight : Qt::RightToLeft; //QPainter::setLayoutDirection( ld ); if(ld == Qt::RightToLeft) // Reverse the text { QString s2; for(int i = s.length() - 1; i >= 0; --i) { s2 += s[i]; } QPainter::drawText(m_xOffset - m_fontWidth * s.length() + m_factor * x, y, s2); return; } QPainter::drawText(m_xOffset - m_fontWidth * s.length() + m_factor * x, y, s); } void drawLine(int x1, int y1, int x2, int y2) { QPainter::drawLine(m_xOffset + m_factor * x1, y1, m_xOffset + m_factor * x2, y2); } }; bool fineDiff( Diff3LineList& diff3LineList, int selector, const LineData* v1, const LineData* v2); bool equal(const LineData& l1, const LineData& l2, bool bStrict); inline bool isWhite(QChar c) { return c == ' ' || c == '\t' || c == '\r'; } /** Returns the number of equivalent spaces at position outPos. */ inline int tabber(int outPos, int tabSize) { return tabSize - (outPos % tabSize); } /** Returns a line number where the linerange [line, line+nofLines] can be displayed best. If it fits into the currently visible range then the returned value is the current firstLine. */ int getBestFirstLine(int line, int nofLines, int firstLine, int visibleLines); extern bool g_bIgnoreWhiteSpace; extern bool g_bIgnoreTrivialMatches; extern int g_bAutoSolve; // Cursor conversions that consider g_tabSize. int convertToPosInText(const QString& s, int posOnScreen, int tabSize); int convertToPosOnScreen(const QString& s, int posInText, int tabSize); enum e_CoordType { eFileCoords, eD3LLineCoords, eWrapCoords }; void calcTokenPos(const QString&, int posOnScreen, int& pos1, int& pos2, int tabSize); QString calcHistorySortKey(const QString& keyOrder, QRegExp& matchedRegExpr, const QStringList& parenthesesGroupList); bool findParenthesesGroups(const QString& s, QStringList& sl); #endif diff --git a/src/mergeresultwindow.cpp b/src/mergeresultwindow.cpp index 16a2d90..3aecccf 100644 --- a/src/mergeresultwindow.cpp +++ b/src/mergeresultwindow.cpp @@ -1,3313 +1,3313 @@ /*************************************************************************** * Copyright (C) 2002-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mergeresultwindow.h" #include "options.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int g_bAutoSolve = true; #undef leftInfoWidth MergeResultWindow::MergeResultWindow( QWidget* pParent, Options* pOptions, QStatusBar* pStatusBar) : QWidget(pParent) { setObjectName("MergeResultWindow"); setFocusPolicy(Qt::ClickFocus); m_firstLine = 0; m_horizScrollOffset = 0; m_nofLines = 0; m_totalSize = 0; m_bMyUpdate = false; m_bInsertMode = true; m_scrollDeltaX = 0; m_scrollDeltaY = 0; m_bModified = false; m_eOverviewMode = Overview::eOMNormal; m_pldA = nullptr; m_pldB = nullptr; m_pldC = nullptr; m_sizeA = 0; m_sizeB = 0; m_sizeC = 0; m_pDiff3LineList = nullptr; m_pTotalDiffStatus = nullptr; m_pStatusBar = pStatusBar; if(m_pStatusBar) connect(m_pStatusBar, &QStatusBar::messageChanged, this, &MergeResultWindow::slotStatusMessageChanged); m_pOptions = pOptions; setUpdatesEnabled(false); m_delayedDrawTimer = 0; m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = 0; m_bCursorOn = true; m_bCursorUpdate = false; m_maxTextWidth = -1; connect(&m_cursorTimer, &QTimer::timeout, this, &MergeResultWindow::slotCursorUpdate); m_cursorTimer.setSingleShot(true); m_cursorTimer.start(500 /*ms*/); m_selection.reset(); setMinimumSize(QSize(20, 20)); setFont(m_pOptions->m_font); } void MergeResultWindow::init( const LineData* pLineDataA, LineRef sizeA, const LineData* pLineDataB, LineRef sizeB, const LineData* pLineDataC, LineRef sizeC, const Diff3LineList* pDiff3LineList, TotalDiffStatus* pTotalDiffStatus) { m_firstLine = 0; m_horizScrollOffset = 0; m_nofLines = 0; m_bMyUpdate = false; m_bInsertMode = true; m_scrollDeltaX = 0; m_scrollDeltaY = 0; setModified(false); m_pldA = pLineDataA; m_pldB = pLineDataB; m_pldC = pLineDataC; m_sizeA = sizeA; m_sizeB = sizeB; m_sizeC = sizeC; m_pDiff3LineList = pDiff3LineList; m_pTotalDiffStatus = pTotalDiffStatus; m_selection.reset(); m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = 0; m_maxTextWidth = -1; merge(g_bAutoSolve, -1); g_bAutoSolve = true; update(); updateSourceMask(); showUnsolvedConflictsStatusMessage(); } void MergeResultWindow::showUnsolvedConflictsStatusMessage() { if(m_pStatusBar) { int wsc; int nofUnsolved = getNrOfUnsolvedConflicts(&wsc); m_persistentStatusMessage = i18n("Number of remaining unsolved conflicts: %1 (of which %2 are whitespace)", nofUnsolved, wsc); m_pStatusBar->showMessage(m_persistentStatusMessage); } } void MergeResultWindow::slotStatusMessageChanged(const QString& s) { if(s.isEmpty() && !m_persistentStatusMessage.isEmpty()) { m_pStatusBar->showMessage(m_persistentStatusMessage, 0); } } void MergeResultWindow::reset() { m_pDiff3LineList = nullptr; m_pTotalDiffStatus = nullptr; m_pldA = nullptr; m_pldB = nullptr; m_pldC = nullptr; if(!m_persistentStatusMessage.isEmpty()) { m_persistentStatusMessage = QString(); } } // Calculate the merge information for the given Diff3Line. // Results will be stored in mergeDetails, bConflict, bLineRemoved and src. void Diff3Line::mergeOneLine( e_MergeDetails& mergeDetails, bool& bConflict, bool& bLineRemoved, int& src, bool bTwoInputs) const { mergeDetails = eDefault; bConflict = false; bLineRemoved = false; src = 0; if(bTwoInputs) // Only two input files { if(getLineA() != -1 && getLineB() != -1) { if(pFineAB == nullptr) { mergeDetails = eNoChange; src = A; } else { mergeDetails = eBChanged; bConflict = true; } } else { if(getLineA() != -1 && getLineB() == -1) { mergeDetails = eBDeleted; bConflict = true; } else if(getLineA() == -1 && getLineB() != -1) { mergeDetails = eBDeleted; bConflict = true; } } return; } // A is base. if(getLineA() != -1 && getLineB() != -1 && getLineC() != -1) { if(pFineAB == nullptr && pFineBC == nullptr && pFineCA == nullptr) { mergeDetails = eNoChange; src = A; } else if(pFineAB == nullptr && pFineBC != nullptr && pFineCA != nullptr) { mergeDetails = eCChanged; src = C; } else if(pFineAB != nullptr && pFineBC != nullptr && pFineCA == nullptr) { mergeDetails = eBChanged; src = B; } else if(pFineAB != nullptr && pFineBC == nullptr && pFineCA != nullptr) { mergeDetails = eBCChangedAndEqual; src = C; } else if(pFineAB != nullptr && pFineBC != nullptr && pFineCA != nullptr) { mergeDetails = eBCChanged; bConflict = true; } else Q_ASSERT(true); } else if(getLineA() != -1 && getLineB() != -1 && getLineC() == -1) { if(pFineAB != nullptr) { mergeDetails = eBChanged_CDeleted; bConflict = true; } else { mergeDetails = eCDeleted; bLineRemoved = true; src = C; } } else if(getLineA() != -1 && getLineB() == -1 && getLineC() != -1) { if(pFineCA != nullptr) { mergeDetails = eCChanged_BDeleted; bConflict = true; } else { mergeDetails = eBDeleted; bLineRemoved = true; src = B; } } else if(getLineA() == -1 && getLineB() != -1 && getLineC() != -1) { if(pFineBC != nullptr) { mergeDetails = eBCAdded; bConflict = true; } else // B==C { mergeDetails = eBCAddedAndEqual; src = C; } } else if(getLineA() == -1 && getLineB() == -1 && getLineC() != -1) { mergeDetails = eCAdded; src = C; } else if(getLineA() == -1 && getLineB() != -1 && getLineC() == -1) { mergeDetails = eBAdded; src = B; } else if(getLineA() != -1 && getLineB() == -1 && getLineC() == -1) { mergeDetails = eBCDeleted; bLineRemoved = true; src = C; } else Q_ASSERT(true); } bool MergeResultWindow::sameKindCheck(const MergeLine& ml1, const MergeLine& ml2) { if(ml1.bConflict && ml2.bConflict) { // Both lines have conflicts: If one is only a white space conflict and // the other one is a real conflict, then this line returns false. - return ml1.id3l->bAEqC == ml2.id3l->bAEqC && ml1.id3l->bAEqB == ml2.id3l->bAEqB; + return ml1.id3l->isEqualAC() == ml2.id3l->isEqualAC() && ml1.id3l->isEqualAB() == ml2.id3l->isEqualAB(); } else return ( (!ml1.bConflict && !ml2.bConflict && ml1.bDelta && ml2.bDelta && ml1.srcSelect == ml2.srcSelect && (ml1.mergeDetails == ml2.mergeDetails || (ml1.mergeDetails != eBCAddedAndEqual && ml2.mergeDetails != eBCAddedAndEqual))) || (!ml1.bDelta && !ml2.bDelta)); } void MergeResultWindow::merge(bool bAutoSolve, int defaultSelector, bool bConflictsOnly, bool bWhiteSpaceOnly) { if(!bConflictsOnly) { if(m_bModified) { int result = KMessageBox::warningYesNo(this, i18n("The output has been modified.\n" "If you continue your changes will be lost."), i18n("Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel()); if(result == KMessageBox::No) return; } m_mergeLineList.clear(); m_totalSize = 0; int lineIdx = 0; Diff3LineList::const_iterator it; for(it = m_pDiff3LineList->begin(); it != m_pDiff3LineList->end(); ++it, ++lineIdx) { const Diff3Line& d = *it; MergeLine ml; bool bLineRemoved; d.mergeOneLine( ml.mergeDetails, ml.bConflict, bLineRemoved, ml.srcSelect, m_pldC == nullptr); // Automatic solving for only whitespace changes. if(ml.bConflict && - ((m_pldC == nullptr && (d.bAEqB || (d.bWhiteLineA && d.bWhiteLineB))) || - (m_pldC != nullptr && ((d.bAEqB && d.bAEqC) || (d.bWhiteLineA && d.bWhiteLineB && d.bWhiteLineC))))) + ((m_pldC == nullptr && (d.isEqualAB() || (d.bWhiteLineA && d.bWhiteLineB))) || + (m_pldC != nullptr && ((d.isEqualAB() && d.isEqualAC()) || (d.bWhiteLineA && d.bWhiteLineB && d.bWhiteLineC))))) { ml.bWhiteSpaceConflict = true; } ml.d3lLineIdx = lineIdx; ml.bDelta = ml.srcSelect != A; ml.id3l = it; ml.srcRangeLength = 1; MergeLine* back = m_mergeLineList.empty() ? nullptr : &m_mergeLineList.back(); bool bSame = back != nullptr && sameKindCheck(ml, *back); if(bSame) { ++back->srcRangeLength; if(back->bWhiteSpaceConflict && !ml.bWhiteSpaceConflict) back->bWhiteSpaceConflict = false; } else { ml.mergeEditLineList.setTotalSizePtr(&m_totalSize); m_mergeLineList.push_back(ml); } if(!ml.bConflict) { MergeLine& tmpBack = m_mergeLineList.back(); MergeEditLine mel(ml.id3l); mel.setSource(ml.srcSelect, bLineRemoved); tmpBack.mergeEditLineList.push_back(mel); } else if(back == nullptr || !back->bConflict || !bSame) { MergeLine& tmpBack = m_mergeLineList.back(); MergeEditLine mel(ml.id3l); mel.setConflict(); tmpBack.mergeEditLineList.push_back(mel); } } } bool bSolveWhiteSpaceConflicts = false; if(bAutoSolve) // when true, then the other params are not used and we can change them here. (see all invocations of merge()) { if(m_pldC == nullptr && m_pOptions->m_whiteSpace2FileMergeDefault != 0) // Only two inputs { defaultSelector = m_pOptions->m_whiteSpace2FileMergeDefault; bWhiteSpaceOnly = true; bSolveWhiteSpaceConflicts = true; } else if(m_pldC != nullptr && m_pOptions->m_whiteSpace3FileMergeDefault != 0) { defaultSelector = m_pOptions->m_whiteSpace3FileMergeDefault; bWhiteSpaceOnly = true; bSolveWhiteSpaceConflicts = true; } } if(!bAutoSolve || bSolveWhiteSpaceConflicts) { // Change all auto selections MergeLineList::iterator mlIt; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; bool bConflict = ml.mergeEditLineList.empty() || ml.mergeEditLineList.begin()->isConflict(); if(ml.bDelta && (!bConflictsOnly || bConflict) && (!bWhiteSpaceOnly || ml.bWhiteSpaceConflict)) { ml.mergeEditLineList.clear(); if(defaultSelector == -1 && ml.bDelta) { MergeEditLine mel(ml.id3l); ; mel.setConflict(); ml.bConflict = true; ml.mergeEditLineList.push_back(mel); } else { Diff3LineList::const_iterator d3llit = ml.id3l; int j; for(j = 0; j < ml.srcRangeLength; ++j) { MergeEditLine mel(d3llit); mel.setSource(defaultSelector, false); LineRef srcLine = defaultSelector == 1 ? d3llit->getLineA() : defaultSelector == 2 ? d3llit->getLineB() : defaultSelector == 3 ? d3llit->getLineC() : -1; if(srcLine != -1) { ml.mergeEditLineList.push_back(mel); } ++d3llit; } if(ml.mergeEditLineList.empty()) // Make a line nevertheless { MergeEditLine mel(ml.id3l); mel.setRemoved(defaultSelector); ml.mergeEditLineList.push_back(mel); } } } } } MergeLineList::iterator mlIt; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; // Remove all lines that are empty, because no src lines are there. LineRef oldSrcLine = -1; int oldSrc = -1; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; int melsrc = mel.src(); LineRef srcLine = mel.isRemoved() ? -1 : melsrc == 1 ? mel.id3l()->getLineA() : melsrc == 2 ? mel.id3l()->getLineB() : melsrc == 3 ? mel.id3l()->getLineC() : -1; // At least one line remains because oldSrc != melsrc for first line in list // Other empty lines will be removed if(srcLine == -1 && oldSrcLine == -1 && oldSrc == melsrc) melIt = ml.mergeEditLineList.erase(melIt); else ++melIt; oldSrcLine = srcLine; oldSrc = melsrc; } } if(bAutoSolve && !bConflictsOnly) { if(m_pOptions->m_bRunHistoryAutoMergeOnMergeStart) slotMergeHistory(); if(m_pOptions->m_bRunRegExpAutoMergeOnMergeStart) slotRegExpAutoMerge(); if(m_pldC != nullptr && !doRelevantChangesExist()) emit noRelevantChangesDetected(); } int nrOfSolvedConflicts = 0; int nrOfUnsolvedConflicts = 0; int nrOfWhiteSpaceConflicts = 0; MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->bConflict) ++nrOfUnsolvedConflicts; else if(i->bDelta) ++nrOfSolvedConflicts; if(i->bWhiteSpaceConflict) ++nrOfWhiteSpaceConflicts; } m_pTotalDiffStatus->setUnsolvedConflicts(nrOfUnsolvedConflicts); m_pTotalDiffStatus->setSolvedConflicts(nrOfSolvedConflicts); m_pTotalDiffStatus->setWhitespaceConflicts(nrOfWhiteSpaceConflicts); m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = 0; m_maxTextWidth = -1; //m_firstLine = 0; // Must not set line/column without scrolling there //m_horizScrollOffset = 0; setModified(false); m_currentMergeLineIt = m_mergeLineList.begin(); slotGoTop(); emit updateAvailabilities(); update(); } void MergeResultWindow::setFirstLine(int firstLine) { m_firstLine = std::max(0, firstLine); update(); } void MergeResultWindow::setHorizScrollOffset(int horizScrollOffset) { m_horizScrollOffset = std::max(0, horizScrollOffset); update(); } int MergeResultWindow::getMaxTextWidth() { if(m_maxTextWidth < 0) { m_maxTextWidth = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; QString s = mel.getString(this); QTextLayout textLayout(s, font(), this); textLayout.beginLayout(); textLayout.createLine(); textLayout.endLayout(); if(m_maxTextWidth < textLayout.maximumWidth()) { m_maxTextWidth = qCeil(textLayout.maximumWidth()); } } } m_maxTextWidth += 5; // cursorwidth } return m_maxTextWidth; } int MergeResultWindow::getNofLines() { return m_totalSize; } int MergeResultWindow::getVisibleTextAreaWidth() { // QFontMetrics fm = fontMetrics(); // FIXME used? return width() - getTextXOffset(); } int MergeResultWindow::getNofVisibleLines() { QFontMetrics fm = fontMetrics(); return (height() - 3) / fm.lineSpacing() - 2; } int MergeResultWindow::getTextXOffset() { QFontMetrics fm = fontMetrics(); return 3 * fm.width('0'); } void MergeResultWindow::resizeEvent(QResizeEvent* e) { QWidget::resizeEvent(e); emit resizeSignal(); } Overview::e_OverviewMode MergeResultWindow::getOverviewMode() { return m_eOverviewMode; } void MergeResultWindow::setOverviewMode(Overview::e_OverviewMode eOverviewMode) { m_eOverviewMode = eOverviewMode; } // Check whether we should ignore current delta when moving to next/previous delta bool MergeResultWindow::checkOverviewIgnore(MergeLineList::iterator& i) { if(m_eOverviewMode == Overview::eOMNormal) return false; if(m_eOverviewMode == Overview::eOMAvsB) return i->mergeDetails == eCAdded || i->mergeDetails == eCDeleted || i->mergeDetails == eCChanged; if(m_eOverviewMode == Overview::eOMAvsC) return i->mergeDetails == eBAdded || i->mergeDetails == eBDeleted || i->mergeDetails == eBChanged; if(m_eOverviewMode == Overview::eOMBvsC) return i->mergeDetails == eBCAddedAndEqual || i->mergeDetails == eBCDeleted || i->mergeDetails == eBCChangedAndEqual; return false; } // Go to prev/next delta/conflict or first/last delta. void MergeResultWindow::go(e_Direction eDir, e_EndPoint eEndPoint) { Q_ASSERT(eDir == eUp || eDir == eDown); MergeLineList::iterator i = m_currentMergeLineIt; bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(eEndPoint == eEnd) { if(eDir == eUp) i = m_mergeLineList.begin(); // first mergeline else i = --m_mergeLineList.end(); // last mergeline while(isItAtEnd(eDir == eUp, i) && !i->bDelta) { if(eDir == eUp) ++i; // search downwards else --i; // search upwards } } else if(eEndPoint == eDelta && isItAtEnd(eDir != eUp, i)) { do { if(eDir == eUp) --i; else ++i; } while(isItAtEnd(eDir != eUp, i) && (!i->bDelta || checkOverviewIgnore(i) || (bSkipWhiteConflicts && i->bWhiteSpaceConflict))); } else if(eEndPoint == eConflict && isItAtEnd(eDir != eUp, i)) { do { if(eDir == eUp) --i; else ++i; } while(isItAtEnd(eDir != eUp, i) && (!i->bConflict || (bSkipWhiteConflicts && i->bWhiteSpaceConflict))); } else if(isItAtEnd(eDir != eUp, i) && eEndPoint == eUnsolvedConflict) { do { if(eDir == eUp) --i; else ++i; } while(isItAtEnd(eDir != eUp, i) && !i->mergeEditLineList.begin()->isConflict()); } if(isVisible()) setFocus(); setFastSelector(i); } bool MergeResultWindow::isDeltaAboveCurrent() { bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i == m_mergeLineList.begin()) return false; do { --i; if(i->bDelta && !checkOverviewIgnore(i) && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } while(i != m_mergeLineList.begin()); return false; } bool MergeResultWindow::isDeltaBelowCurrent() { bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i != m_mergeLineList.end()) { ++i; for(; i != m_mergeLineList.end(); ++i) { if(i->bDelta && !checkOverviewIgnore(i) && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } } return false; } bool MergeResultWindow::isConflictAboveCurrent() { if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i == m_mergeLineList.begin()) return false; bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; do { --i; if(i->bConflict && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } while(i != m_mergeLineList.begin()); return false; } bool MergeResultWindow::isConflictBelowCurrent() { MergeLineList::iterator i = m_currentMergeLineIt; if(m_mergeLineList.empty()) return false; bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(i != m_mergeLineList.end()) { ++i; for(; i != m_mergeLineList.end(); ++i) { if(i->bConflict && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } } return false; } bool MergeResultWindow::isUnsolvedConflictAtCurrent() { if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; return i->mergeEditLineList.begin()->isConflict(); } bool MergeResultWindow::isUnsolvedConflictAboveCurrent() { if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i == m_mergeLineList.begin()) return false; do { --i; if(i->mergeEditLineList.begin()->isConflict()) return true; } while(i != m_mergeLineList.begin()); return false; } bool MergeResultWindow::isUnsolvedConflictBelowCurrent() { MergeLineList::iterator i = m_currentMergeLineIt; if(m_mergeLineList.empty()) return false; if(i != m_mergeLineList.end()) { ++i; for(; i != m_mergeLineList.end(); ++i) { if(i->mergeEditLineList.begin()->isConflict()) return true; } } return false; } void MergeResultWindow::slotGoTop() { go(eUp, eEnd); } void MergeResultWindow::slotGoCurrent() { setFastSelector(m_currentMergeLineIt); } void MergeResultWindow::slotGoBottom() { go(eDown, eEnd); } void MergeResultWindow::slotGoPrevDelta() { go(eUp, eDelta); } void MergeResultWindow::slotGoNextDelta() { go(eDown, eDelta); } void MergeResultWindow::slotGoPrevConflict() { go(eUp, eConflict); } void MergeResultWindow::slotGoNextConflict() { go(eDown, eConflict); } void MergeResultWindow::slotGoPrevUnsolvedConflict() { go(eUp, eUnsolvedConflict); } void MergeResultWindow::slotGoNextUnsolvedConflict() { go(eDown, eUnsolvedConflict); } /** The line is given as a index in the Diff3LineList. The function calculates the corresponding iterator. */ void MergeResultWindow::slotSetFastSelectorLine(int line) { MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(line >= i->d3lLineIdx && line < i->d3lLineIdx + i->srcRangeLength) { //if ( i->bDelta ) { setFastSelector(i); } break; } } } int MergeResultWindow::getNrOfUnsolvedConflicts(int* pNrOfWhiteSpaceConflicts) { int nrOfUnsolvedConflicts = 0; if(pNrOfWhiteSpaceConflicts != nullptr) *pNrOfWhiteSpaceConflicts = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt = ml.mergeEditLineList.begin(); if(melIt->isConflict()) { ++nrOfUnsolvedConflicts; if(ml.bWhiteSpaceConflict && pNrOfWhiteSpaceConflicts != nullptr) ++*pNrOfWhiteSpaceConflicts; } } return nrOfUnsolvedConflicts; } void MergeResultWindow::showNrOfConflicts() { if(!m_pOptions->m_bShowInfoDialogs) return; int nrOfConflicts = 0; MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->bConflict || i->bDelta) ++nrOfConflicts; } QString totalInfo; if(m_pTotalDiffStatus->bBinaryAEqB && m_pTotalDiffStatus->bBinaryAEqC) totalInfo += i18n("All input files are binary equal."); else if(m_pTotalDiffStatus->bTextAEqB && m_pTotalDiffStatus->bTextAEqC) totalInfo += i18n("All input files contain the same text."); else { if(m_pTotalDiffStatus->bBinaryAEqB) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("B")); else if(m_pTotalDiffStatus->bTextAEqB) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("B")); if(m_pTotalDiffStatus->bBinaryAEqC) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("C")); else if(m_pTotalDiffStatus->bTextAEqC) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("C")); if(m_pTotalDiffStatus->bBinaryBEqC) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("B"), i18n("C")); else if(m_pTotalDiffStatus->bTextBEqC) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("B"), i18n("C")); } int nrOfUnsolvedConflicts = getNrOfUnsolvedConflicts(); KMessageBox::information(this, i18n("Total number of conflicts: %1\n" "Nr of automatically solved conflicts: %2\n" "Nr of unsolved conflicts: %3\n" "%4", nrOfConflicts, nrOfConflicts - nrOfUnsolvedConflicts, nrOfUnsolvedConflicts, totalInfo), i18n("Conflicts")); } void MergeResultWindow::setFastSelector(MergeLineList::iterator i) { if(i == m_mergeLineList.end()) return; m_currentMergeLineIt = i; emit setFastSelectorRange(i->d3lLineIdx, i->srcRangeLength); int line1 = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { if(mlIt == m_currentMergeLineIt) break; line1 += mlIt->mergeEditLineList.size(); } int nofLines = m_currentMergeLineIt->mergeEditLineList.size(); int newFirstLine = getBestFirstLine(line1, nofLines, m_firstLine, getNofVisibleLines()); if(newFirstLine != m_firstLine) { emit scrollMergeResultWindow(0, newFirstLine - m_firstLine); } if(m_selection.isEmpty()) { m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = line1; } update(); updateSourceMask(); emit updateAvailabilities(); } void MergeResultWindow::choose(int selector) { if(m_currentMergeLineIt == m_mergeLineList.end()) return; setModified(); // First find range for which this change works. MergeLine& ml = *m_currentMergeLineIt; MergeEditLineList::iterator melIt; // Now check if selector is active for this range already. bool bActive = false; // Remove unneeded lines in the range. for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; if(mel.src() == selector) bActive = true; if(mel.src() == selector || !mel.isEditableText() || mel.isModified()) melIt = ml.mergeEditLineList.erase(melIt); else ++melIt; } if(!bActive) // Selected source wasn't active. { // Append the lines from selected source here at rangeEnd. Diff3LineList::const_iterator d3llit = ml.id3l; int j; for(j = 0; j < ml.srcRangeLength; ++j) { MergeEditLine mel(d3llit); mel.setSource(selector, false); ml.mergeEditLineList.push_back(mel); ++d3llit; } } if(!ml.mergeEditLineList.empty()) { // Remove all lines that are empty, because no src lines are there. for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; LineRef srcLine = mel.src() == 1 ? mel.id3l()->getLineA() : mel.src() == 2 ? mel.id3l()->getLineB() : mel.src() == 3 ? mel.id3l()->getLineC() : -1; if(srcLine == -1) melIt = ml.mergeEditLineList.erase(melIt); else ++melIt; } } if(ml.mergeEditLineList.empty()) { // Insert a dummy line: MergeEditLine mel(ml.id3l); if(bActive) mel.setConflict(); // All src entries deleted => conflict else mel.setRemoved(selector); // No lines in corresponding src found. ml.mergeEditLineList.push_back(mel); } if(m_cursorYPos >= m_totalSize) { m_cursorYPos = m_totalSize - 1; m_cursorXPos = 0; } m_maxTextWidth = -1; update(); updateSourceMask(); emit updateAvailabilities(); showUnsolvedConflictsStatusMessage(); } // bConflictsOnly: automatically choose for conflicts only (true) or for everywhere (false) void MergeResultWindow::chooseGlobal(int selector, bool bConflictsOnly, bool bWhiteSpaceOnly) { resetSelection(); merge(false, selector, bConflictsOnly, bWhiteSpaceOnly); setModified(true); update(); showUnsolvedConflictsStatusMessage(); } void MergeResultWindow::slotAutoSolve() { resetSelection(); merge(true, -1); setModified(true); update(); showUnsolvedConflictsStatusMessage(); } void MergeResultWindow::slotUnsolve() { resetSelection(); merge(false, -1); setModified(true); update(); showUnsolvedConflictsStatusMessage(); } static QString calcHistoryLead(const QString& s) { // Return the start of the line until the first white char after the first non white char. int i; for(i = 0; i < s.length(); ++i) { if(s[i] != ' ' && s[i] != '\t') { for(; i < s.length(); ++i) { if(s[i] == ' ' || s[i] == '\t') { return s.left(i); } } return s; // Very unlikely } } return QString(); // Must be an empty string, not a null string. } static void findHistoryRange(const QRegExp& historyStart, bool bThreeFiles, const Diff3LineList* pD3LList, Diff3LineList::const_iterator& iBegin, Diff3LineList::const_iterator& iEnd, int& idxBegin, int& idxEnd) { QString historyLead; // Search for start of history for(iBegin = pD3LList->begin(), idxBegin = 0; iBegin != pD3LList->end(); ++iBegin, ++idxBegin) { if(historyStart.exactMatch(iBegin->getString(A)) && historyStart.exactMatch(iBegin->getString(B)) && (!bThreeFiles || historyStart.exactMatch(iBegin->getString(C)))) { historyLead = calcHistoryLead(iBegin->getString(A)); break; } } // Search for end of history for(iEnd = iBegin, idxEnd = idxBegin; iEnd != pD3LList->end(); ++iEnd, ++idxEnd) { QString sA = iEnd->getString(A); QString sB = iEnd->getString(B); QString sC = iEnd->getString(C); if(!((sA.isEmpty() || historyLead == calcHistoryLead(sA)) && (sB.isEmpty() || historyLead == calcHistoryLead(sB)) && (!bThreeFiles || sC.isEmpty() || historyLead == calcHistoryLead(sC)))) { break; // End of the history } } } bool findParenthesesGroups(const QString& s, QStringList& sl) { sl.clear(); int i = 0; std::list startPosStack; int length = s.length(); for(i = 0; i < length; ++i) { if(s[i] == '\\' && i + 1 < length && (s[i + 1] == '\\' || s[i + 1] == '(' || s[i + 1] == ')')) { ++i; continue; } if(s[i] == '(') { startPosStack.push_back(i); } else if(s[i] == ')') { if(startPosStack.empty()) return false; // Parentheses don't match int startPos = startPosStack.back(); startPosStack.pop_back(); sl.push_back(s.mid(startPos + 1, i - startPos - 1)); } } return startPosStack.empty(); // false if parentheses don't match } QString calcHistorySortKey(const QString& keyOrder, QRegExp& matchedRegExpr, const QStringList& parenthesesGroupList) { QStringList keyOrderList = keyOrder.split(','); QString key; for(QStringList::iterator keyIt = keyOrderList.begin(); keyIt != keyOrderList.end(); ++keyIt) { if((*keyIt).isEmpty()) continue; bool bOk = false; int groupIdx = (*keyIt).toInt(&bOk); if(!bOk || groupIdx < 0 || groupIdx > (int)parenthesesGroupList.size()) continue; QString s = matchedRegExpr.cap(groupIdx); if(groupIdx == 0) { key += s + ' '; continue; } QString groupRegExp = parenthesesGroupList[groupIdx - 1]; if(groupRegExp.indexOf('|') < 0 || groupRegExp.indexOf('(') >= 0) { bOk = false; int i = s.toInt(&bOk); if(bOk && i >= 0 && i < 10000) s.sprintf("%04d", i); // This should help for correct sorting of numbers. key += s + ' '; } else { // Assume that the groupRegExp consists of something like "Jan|Feb|Mar|Apr" // s is the string that managed to match. // Now we want to know at which position it occurred. e.g. Jan=0, Feb=1, Mar=2, etc. QStringList sl = groupRegExp.split('|'); int idx = sl.indexOf(s); if(idx < 0) { // Didn't match } else { QString sIdx; sIdx.sprintf("%02d", idx + 1); // Up to 99 words in the groupRegExp (more than 12 aren't expected) key += sIdx + ' '; } } } return key; } void MergeResultWindow::collectHistoryInformation( int src, Diff3LineList::const_iterator &iHistoryBegin, Diff3LineList::const_iterator &iHistoryEnd, HistoryMap& historyMap, std::list& hitList // list of iterators ) { std::list::iterator itHitListFront = hitList.begin(); Diff3LineList::const_iterator id3l = iHistoryBegin; QString historyLead; { const LineData* pld = id3l->getLineData(src); QString s(pld->getLine(), pld->size()); historyLead = calcHistoryLead(s); } QRegExp historyStart(m_pOptions->m_historyStartRegExp); if(id3l == iHistoryEnd) return; ++id3l; // Skip line with "$Log ... $" QRegExp newHistoryEntry(m_pOptions->m_historyEntryStartRegExp); QStringList parenthesesGroups; findParenthesesGroups(m_pOptions->m_historyEntryStartRegExp, parenthesesGroups); QString key; MergeEditLineList melList; bool bPrevLineIsEmpty = true; bool bUseRegExp = !m_pOptions->m_historyEntryStartRegExp.isEmpty(); for(; id3l != iHistoryEnd; ++id3l) { const LineData* pld = id3l->getLineData(src); if(!pld) continue; QString s(pld->getLine(), pld->size()); if(historyLead.isEmpty()) historyLead = calcHistoryLead(s); QString sLine = s.mid(historyLead.length()); if((!bUseRegExp && !sLine.trimmed().isEmpty() && bPrevLineIsEmpty) || (bUseRegExp && newHistoryEntry.exactMatch(sLine))) { if(!key.isEmpty() && !melList.empty()) { // Only insert new HistoryMapEntry if key not found; in either case p.first is a valid iterator to element key. std::pair p = historyMap.insert(HistoryMap::value_type(key, HistoryMapEntry())); HistoryMapEntry& hme = p.first->second; if(src == A) hme.mellA = melList; if(src == B) hme.mellB = melList; if(src == C) hme.mellC = melList; if(p.second) // Not in list yet? { hitList.insert(itHitListFront, p.first); } } if(!bUseRegExp) key = sLine; else key = calcHistorySortKey(m_pOptions->m_historyEntryStartSortKeyOrder, newHistoryEntry, parenthesesGroups); melList.clear(); melList.push_back(MergeEditLine(id3l, src)); } else if(!historyStart.exactMatch(s)) { melList.push_back(MergeEditLine(id3l, src)); } bPrevLineIsEmpty = sLine.trimmed().isEmpty(); } if(!key.isEmpty()) { // Only insert new HistoryMapEntry if key not found; in either case p.first is a valid iterator to element key. std::pair p = historyMap.insert(HistoryMap::value_type(key, HistoryMapEntry())); HistoryMapEntry& hme = p.first->second; if(src == A) hme.mellA = melList; if(src == B) hme.mellB = melList; if(src == C) hme.mellC = melList; if(p.second) // Not in list yet? { hitList.insert(itHitListFront, p.first); } } // End of the history } MergeResultWindow::MergeEditLineList& MergeResultWindow::HistoryMapEntry::choice(bool bThreeInputs) { if(!bThreeInputs) return mellA.empty() ? mellB : mellA; else { if(mellA.empty()) return mellC.empty() ? mellB : mellC; // A doesn't exist, return one that exists else if(!mellB.empty() && !mellC.empty()) { // A, B and C exist return mellA; } else return mellB.empty() ? mellB : mellC; // A exists, return the one that doesn't exist } } bool MergeResultWindow::HistoryMapEntry::staysInPlace(bool bThreeInputs, Diff3LineList::const_iterator& iHistoryEnd) { // The entry should stay in place if the decision made by the automerger is correct. Diff3LineList::const_iterator& iHistoryLast = iHistoryEnd; --iHistoryLast; if(!bThreeInputs) { if(!mellA.empty() && !mellB.empty() && mellA.begin()->id3l() == mellB.begin()->id3l() && mellA.back().id3l() == iHistoryLast && mellB.back().id3l() == iHistoryLast) { iHistoryEnd = mellA.begin()->id3l(); return true; } else { return false; } } else { if(!mellA.empty() && !mellB.empty() && !mellC.empty() && mellA.begin()->id3l() == mellB.begin()->id3l() && mellA.begin()->id3l() == mellC.begin()->id3l() && mellA.back().id3l() == iHistoryLast && mellB.back().id3l() == iHistoryLast && mellC.back().id3l() == iHistoryLast) { iHistoryEnd = mellA.begin()->id3l(); return true; } else { return false; } } } void MergeResultWindow::slotMergeHistory() { Diff3LineList::const_iterator iD3LHistoryBegin; Diff3LineList::const_iterator iD3LHistoryEnd; int d3lHistoryBeginLineIdx = -1; int d3lHistoryEndLineIdx = -1; // Search for history start, history end in the diff3LineList findHistoryRange(QRegExp(m_pOptions->m_historyStartRegExp), m_pldC != nullptr, m_pDiff3LineList, iD3LHistoryBegin, iD3LHistoryEnd, d3lHistoryBeginLineIdx, d3lHistoryEndLineIdx); if(iD3LHistoryBegin != m_pDiff3LineList->end()) { // Now collect the historyMap information HistoryMap historyMap; std::list hitList; if(m_pldC == nullptr) { collectHistoryInformation(A, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(B, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); } else { collectHistoryInformation(A, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(B, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(C, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); } Diff3LineList::const_iterator iD3LHistoryOrigEnd = iD3LHistoryEnd; bool bHistoryMergeSorting = m_pOptions->m_bHistoryMergeSorting && !m_pOptions->m_historyEntryStartSortKeyOrder.isEmpty() && !m_pOptions->m_historyEntryStartRegExp.isEmpty(); if(m_pOptions->m_maxNofHistoryEntries == -1) { // Remove parts from the historyMap and hitList that stay in place if(bHistoryMergeSorting) { while(!historyMap.empty()) { HistoryMap::iterator hMapIt = historyMap.begin(); if(hMapIt->second.staysInPlace(m_pldC != nullptr, iD3LHistoryEnd)) historyMap.erase(hMapIt); else break; } } else { while(!hitList.empty()) { HistoryMap::iterator hMapIt = hitList.back(); if(hMapIt->second.staysInPlace(m_pldC != nullptr, iD3LHistoryEnd)) hitList.pop_back(); else break; } } while(iD3LHistoryOrigEnd != iD3LHistoryEnd) { --iD3LHistoryOrigEnd; --d3lHistoryEndLineIdx; } } MergeLineList::iterator iMLLStart = splitAtDiff3LineIdx(d3lHistoryBeginLineIdx); MergeLineList::iterator iMLLEnd = splitAtDiff3LineIdx(d3lHistoryEndLineIdx); // Now join all MergeLines in the history MergeLineList::iterator i = iMLLStart; if(i != iMLLEnd) { ++i; while(i != iMLLEnd) { iMLLStart->join(*i); i = m_mergeLineList.erase(i); } } iMLLStart->mergeEditLineList.clear(); // Now insert the complete history into the first MergeLine of the history iMLLStart->mergeEditLineList.push_back(MergeEditLine(iD3LHistoryBegin, m_pldC == nullptr ? B : C)); QString lead = calcHistoryLead(iD3LHistoryBegin->getString(A)); MergeEditLine mel(m_pDiff3LineList->end()); mel.setString(lead); iMLLStart->mergeEditLineList.push_back(mel); int historyCount = 0; if(bHistoryMergeSorting) { // Create a sorted history HistoryMap::reverse_iterator hmit; for(hmit = historyMap.rbegin(); hmit != historyMap.rend(); ++hmit) { if(historyCount == m_pOptions->m_maxNofHistoryEntries) break; ++historyCount; HistoryMapEntry& hme = hmit->second; MergeEditLineList& mell = hme.choice(m_pldC != nullptr); if(!mell.empty()) iMLLStart->mergeEditLineList.splice(iMLLStart->mergeEditLineList.end(), mell, mell.begin(), mell.end()); } } else { // Create history in order of appearance std::list::iterator hlit; for(hlit = hitList.begin(); hlit != hitList.end(); ++hlit) { if(historyCount == m_pOptions->m_maxNofHistoryEntries) break; ++historyCount; HistoryMapEntry& hme = (*hlit)->second; MergeEditLineList& mell = hme.choice(m_pldC != nullptr); if(!mell.empty()) iMLLStart->mergeEditLineList.splice(iMLLStart->mergeEditLineList.end(), mell, mell.begin(), mell.end()); } // If the end of start is empty and the first line at the end is empty remove the last line of start if(!iMLLStart->mergeEditLineList.empty() && !iMLLEnd->mergeEditLineList.empty()) { QString lastLineOfStart = iMLLStart->mergeEditLineList.back().getString(this); QString firstLineOfEnd = iMLLEnd->mergeEditLineList.front().getString(this); if(lastLineOfStart.mid(lead.length()).trimmed().isEmpty() && firstLineOfEnd.mid(lead.length()).trimmed().isEmpty()) iMLLStart->mergeEditLineList.pop_back(); } } setFastSelector(iMLLStart); update(); } } void MergeResultWindow::slotRegExpAutoMerge() { if(m_pOptions->m_autoMergeRegExp.isEmpty()) return; QRegExp vcsKeywords(m_pOptions->m_autoMergeRegExp); MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->bConflict) { Diff3LineList::const_iterator id3l = i->id3l; if(vcsKeywords.exactMatch(id3l->getString(A)) && vcsKeywords.exactMatch(id3l->getString(B)) && (m_pldC == nullptr || vcsKeywords.exactMatch(id3l->getString(C)))) { MergeEditLine& mel = *i->mergeEditLineList.begin(); mel.setSource(m_pldC == nullptr ? B : C, false); splitAtDiff3LineIdx(i->d3lLineIdx + 1); } } } update(); } // This doesn't detect user modifications and should only be called after automatic merge // This will only do something for three file merge. // Irrelevant changes are those where all contributions from B are already contained in C. // Also irrelevant are conflicts automatically solved (automerge regexp and history automerge) // Precondition: The VCS-keyword would also be C. bool MergeResultWindow::doRelevantChangesExist() { if(m_pldC == nullptr || m_mergeLineList.size() <= 1) return true; MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if((i->bConflict && i->mergeEditLineList.begin()->src() != C) || i->srcSelect == B) { return true; } } return false; } // Returns the iterator to the MergeLine after the split MergeResultWindow::MergeLineList::iterator MergeResultWindow::splitAtDiff3LineIdx(int d3lLineIdx) { MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->d3lLineIdx == d3lLineIdx) { // No split needed, this is the beginning of a MergeLine return i; } else if(i->d3lLineIdx > d3lLineIdx) { // The split must be in the previous MergeLine --i; MergeLine& ml = *i; MergeLine newML; ml.split(newML, d3lLineIdx); ++i; return m_mergeLineList.insert(i, newML); } } // The split must be in the previous MergeLine --i; MergeLine& ml = *i; MergeLine newML; ml.split(newML, d3lLineIdx); ++i; return m_mergeLineList.insert(i, newML); } void MergeResultWindow::slotSplitDiff(int firstD3lLineIdx, int lastD3lLineIdx) { if(lastD3lLineIdx >= 0) splitAtDiff3LineIdx(lastD3lLineIdx + 1); setFastSelector(splitAtDiff3LineIdx(firstD3lLineIdx)); } void MergeResultWindow::slotJoinDiffs(int firstD3lLineIdx, int lastD3lLineIdx) { MergeLineList::iterator i; MergeLineList::iterator iMLLStart = m_mergeLineList.end(); MergeLineList::iterator iMLLEnd = m_mergeLineList.end(); for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { MergeLine& ml = *i; if(firstD3lLineIdx >= ml.d3lLineIdx && firstD3lLineIdx < ml.d3lLineIdx + ml.srcRangeLength) { iMLLStart = i; } if(lastD3lLineIdx >= ml.d3lLineIdx && lastD3lLineIdx < ml.d3lLineIdx + ml.srcRangeLength) { iMLLEnd = i; ++iMLLEnd; break; } } bool bJoined = false; for(i = iMLLStart; i != iMLLEnd && i != m_mergeLineList.end();) { if(i == iMLLStart) { ++i; } else { iMLLStart->join(*i); i = m_mergeLineList.erase(i); bJoined = true; } } if(bJoined) { iMLLStart->mergeEditLineList.clear(); // Insert a conflict line as placeholder iMLLStart->mergeEditLineList.push_back(MergeEditLine(iMLLStart->id3l)); } setFastSelector(iMLLStart); } void MergeResultWindow::myUpdate(int afterMilliSecs) { if(m_delayedDrawTimer) killTimer(m_delayedDrawTimer); m_bMyUpdate = true; m_delayedDrawTimer = startTimer(afterMilliSecs); } void MergeResultWindow::timerEvent(QTimerEvent*) { killTimer(m_delayedDrawTimer); m_delayedDrawTimer = 0; if(m_bMyUpdate) { update(); m_bMyUpdate = false; } if(m_scrollDeltaX != 0 || m_scrollDeltaY != 0) { m_selection.end(m_selection.getLastLine() + m_scrollDeltaY, m_selection.getLastPos() + m_scrollDeltaX); emit scrollMergeResultWindow(m_scrollDeltaX, m_scrollDeltaY); killTimer(m_delayedDrawTimer); m_delayedDrawTimer = startTimer(50); } } QString MergeResultWindow::MergeEditLine::getString(const MergeResultWindow* mrw) { if(isRemoved()) { return QString(); } if(!isModified()) { int src = m_src; if(src == 0) { return QString(); } const Diff3Line& d3l = *m_id3l; const LineData* pld = nullptr; Q_ASSERT(src == A || src == B || src == C); if(src == A && d3l.getLineA() != -1) pld = &mrw->m_pldA[d3l.getLineA()]; else if(src == B && d3l.getLineB() != -1) pld = &mrw->m_pldB[d3l.getLineB()]; else if(src == C && d3l.getLineC() != -1) pld = &mrw->m_pldC[d3l.getLineC()]; //Not an error. if(pld == nullptr) { return QString(); } return QString(pld->getLine(), pld->size()); } else { return m_str; } return QString(); } /// Converts the cursor-posOnScreen into a text index, considering tabulators. int convertToPosInText(const QString& /*s*/, int posOnScreen, int /*tabSize*/) { return posOnScreen; } // int localPosOnScreen = 0; // int size=s.length(); // for ( int i=0; i=posOnScreen ) // return i; // // All letters except tabulator have width one. // int letterWidth = s[i]!='\t' ? 1 : tabber( localPosOnScreen, tabSize ); // localPosOnScreen += letterWidth; // if ( localPosOnScreen>posOnScreen ) // return i; // } // return size; //} /// Converts the index into the text to a cursor-posOnScreen considering tabulators. int convertToPosOnScreen(const QString& /*p*/, int posInText, int /*tabSize*/) { return posInText; } // int posOnScreen = 0; // for ( int i=0; i MergeResultWindow::getTextLayoutForLine(int line, const QString& str, QTextLayout& textLayout) { // tabs QTextOption textOption; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) textOption.setTabStop(QFontMetricsF(font()).width(' ') * m_pOptions->m_tabSize); #else textOption.setTabStopDistance(QFontMetricsF(font()).width(' ') * m_pOptions->m_tabSize); #endif if(m_pOptions->m_bShowWhiteSpaceCharacters) { textOption.setFlags(QTextOption::ShowTabsAndSpaces); } textLayout.setTextOption(textOption); if(m_pOptions->m_bShowWhiteSpaceCharacters) { // This additional format is only necessary for the tab arrow QVector formats; QTextLayout::FormatRange formatRange; formatRange.start = 0; formatRange.length = str.length(); formatRange.format.setFont(font()); formats.append(formatRange); textLayout.setFormats(formats); } QVector selectionFormat; textLayout.beginLayout(); if(m_selection.lineWithin(line)) { int firstPosInText = convertToPosInText(str, m_selection.firstPosInLine(line), m_pOptions->m_tabSize); int lastPosInText = convertToPosInText(str, m_selection.lastPosInLine(line), m_pOptions->m_tabSize); int lengthInText = std::max(0, lastPosInText - firstPosInText); if(lengthInText > 0) m_selection.bSelectionContainsData = true; QTextLayout::FormatRange selection; selection.start = firstPosInText; selection.length = lengthInText; selection.format.setBackground(palette().highlight()); selection.format.setForeground(palette().highlightedText().color()); selectionFormat.push_back(selection); } QTextLine textLine = textLayout.createLine(); textLine.setPosition(QPointF(0, fontMetrics().leading())); textLayout.endLayout(); int cursorWidth = 5; if(m_pOptions->m_bRightToLeftLanguage) textLayout.setPosition(QPointF(width() - textLayout.maximumWidth() - getTextXOffset() + m_horizScrollOffset - cursorWidth, 0)); else textLayout.setPosition(QPointF(getTextXOffset() - m_horizScrollOffset, 0)); return selectionFormat; } void MergeResultWindow::writeLine( MyPainter& p, int line, const QString& str, int srcSelect, e_MergeDetails mergeDetails, int rangeMark, bool bUserModified, bool bLineRemoved, bool bWhiteSpaceConflict) { const QFontMetrics& fm = fontMetrics(); int fontHeight = fm.lineSpacing(); int fontAscent = fm.ascent(); int topLineYOffset = 0; int xOffset = getTextXOffset(); int yOffset = (line - m_firstLine) * fontHeight; if(yOffset < 0 || yOffset > height()) return; yOffset += topLineYOffset; QString srcName = QChar(' '); if(bUserModified) srcName = QChar('m'); else if(srcSelect == A && mergeDetails != eNoChange) srcName = i18n("A"); else if(srcSelect == B) srcName = i18n("B"); else if(srcSelect == C) srcName = i18n("C"); if(rangeMark & 4) { p.fillRect(xOffset, yOffset, width(), fontHeight, m_pOptions->m_currentRangeBgColor); } if((srcSelect > 0 || bUserModified) && !bLineRemoved) { if(!m_pOptions->m_bRightToLeftLanguage) p.setClipRect(QRectF(xOffset, 0, width() - xOffset, height())); else p.setClipRect(QRectF(0, 0, width() - xOffset, height())); int outPos = 0; QString s; int size = str.length(); for(int i = 0; i < size; ++i) { int spaces = 1; if(str[i] == '\t') { spaces = tabber(outPos, m_pOptions->m_tabSize); for(int j = 0; j < spaces; ++j) s += ' '; } else { s += str[i]; } outPos += spaces; } p.setPen(m_pOptions->m_fgColor); QTextLayout textLayout(str, font(), this); QVector selectionFormat = getTextLayoutForLine(line, str, textLayout); textLayout.draw(&p, QPointF(0, yOffset), selectionFormat); if(line == m_cursorYPos) { m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(m_cursorXPos)); if(m_pOptions->m_bRightToLeftLanguage) m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset); } p.setClipping(false); p.setPen(m_pOptions->m_fgColor); p.drawText(1, yOffset + fontAscent, srcName, true); } else if(bLineRemoved) { p.setPen(m_pOptions->m_colorForConflict); p.drawText(xOffset, yOffset + fontAscent, i18n("")); p.drawText(1, yOffset + fontAscent, srcName); if(m_cursorYPos == line) m_cursorXPos = 0; } else if(srcSelect == 0) { p.setPen(m_pOptions->m_colorForConflict); if(bWhiteSpaceConflict) p.drawText(xOffset, yOffset + fontAscent, i18n("")); else p.drawText(xOffset, yOffset + fontAscent, i18n("")); p.drawText(1, yOffset + fontAscent, "?"); if(m_cursorYPos == line) m_cursorXPos = 0; } else Q_ASSERT(true); xOffset -= fm.width('0'); p.setPen(m_pOptions->m_fgColor); if(rangeMark & 1) // begin mark { p.drawLine(xOffset, yOffset + 1, xOffset, yOffset + fontHeight / 2); p.drawLine(xOffset, yOffset + 1, xOffset - 2, yOffset + 1); } else { p.drawLine(xOffset, yOffset, xOffset, yOffset + fontHeight / 2); } if(rangeMark & 2) // end mark { p.drawLine(xOffset, yOffset + fontHeight / 2, xOffset, yOffset + fontHeight - 1); p.drawLine(xOffset, yOffset + fontHeight - 1, xOffset - 2, yOffset + fontHeight - 1); } else { p.drawLine(xOffset, yOffset + fontHeight / 2, xOffset, yOffset + fontHeight); } if(rangeMark & 4) { p.fillRect(xOffset + 3, yOffset, 3, fontHeight, m_pOptions->m_fgColor); /* p.setPen( blue ); p.drawLine( xOffset+2, yOffset, xOffset+2, yOffset+fontHeight-1 ); p.drawLine( xOffset+3, yOffset, xOffset+3, yOffset+fontHeight-1 );*/ } } void MergeResultWindow::setPaintingAllowed(bool bPaintingAllowed) { setUpdatesEnabled(bPaintingAllowed); if(!bPaintingAllowed) { m_currentMergeLineIt = m_mergeLineList.end(); reset(); } else update(); } void MergeResultWindow::paintEvent(QPaintEvent*) { if(m_pDiff3LineList == nullptr) return; bool bOldSelectionContainsData = m_selection.selectionContainsData(); const QFontMetrics& fm = fontMetrics(); int fontWidth = fm.width('0'); if(!m_bCursorUpdate) // Don't redraw everything for blinking cursor? { m_selection.bSelectionContainsData = false; if(size() != m_pixmap.size()) m_pixmap = QPixmap(size()); MyPainter p(&m_pixmap, m_pOptions->m_bRightToLeftLanguage, width(), fontWidth); p.setFont(font()); p.QPainter::fillRect(rect(), m_pOptions->m_bgColor); //int visibleLines = height() / fontHeight; int lastVisibleLine = m_firstLine + getNofVisibleLines() + 5; int line = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; if(line > lastVisibleLine || line + ml.mergeEditLineList.size() < m_firstLine) { line += ml.mergeEditLineList.size(); } else { MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { if(line >= m_firstLine && line <= lastVisibleLine) { MergeEditLine& mel = *melIt; MergeEditLineList::iterator melIt1 = melIt; ++melIt1; int rangeMark = 0; if(melIt == ml.mergeEditLineList.begin()) rangeMark |= 1; // Begin range mark if(melIt1 == ml.mergeEditLineList.end()) rangeMark |= 2; // End range mark if(mlIt == m_currentMergeLineIt) rangeMark |= 4; // Mark of the current line QString s; s = mel.getString(this); writeLine(p, line, s, mel.src(), ml.mergeDetails, rangeMark, mel.isModified(), mel.isRemoved(), ml.bWhiteSpaceConflict); } ++line; } } } if(line != m_nofLines) { m_nofLines = line; Q_ASSERT(m_nofLines == m_totalSize); emit resizeSignal(); } p.end(); } QPainter painter(this); if(!m_bCursorUpdate) painter.drawPixmap(0, 0, m_pixmap); else { painter.drawPixmap(0, 0, m_pixmap); // Draw everything. (Internally cursor rect is clipped anyway.) m_bCursorUpdate = false; } if(m_bCursorOn && hasFocus() && m_cursorYPos >= m_firstLine) { painter.setPen(m_pOptions->m_fgColor); QString str = getString(m_cursorYPos); QTextLayout textLayout(str, font(), this); getTextLayoutForLine(m_cursorYPos, str, textLayout); textLayout.drawCursor(&painter, QPointF(0, (m_cursorYPos - m_firstLine) * fontMetrics().lineSpacing()), m_cursorXPos); } painter.end(); if(!bOldSelectionContainsData && m_selection.selectionContainsData()) emit newSelection(); } void MergeResultWindow::updateSourceMask() { int srcMask = 0; int enabledMask = 0; if(!hasFocus() || m_pDiff3LineList == nullptr || !updatesEnabled() || m_currentMergeLineIt == m_mergeLineList.end()) { srcMask = 0; enabledMask = 0; } else { enabledMask = m_pldC == nullptr ? 3 : 7; MergeLine& ml = *m_currentMergeLineIt; srcMask = 0; bool bModified = false; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(mel.src() == 1) srcMask |= 1; if(mel.src() == 2) srcMask |= 2; if(mel.src() == 3) srcMask |= 4; if(mel.isModified() || !mel.isEditableText()) bModified = true; } if(ml.mergeDetails == eNoChange) { srcMask = 0; enabledMask = bModified ? 1 : 0; } } emit sourceMask(srcMask, enabledMask); } void MergeResultWindow::focusInEvent(QFocusEvent* e) { updateSourceMask(); QWidget::focusInEvent(e); } int MergeResultWindow::convertToLine(int y) { const QFontMetrics& fm = fontMetrics(); int fontHeight = fm.lineSpacing(); int topLineYOffset = 0; int yOffset = topLineYOffset - m_firstLine * fontHeight; int line = std::min((y - yOffset) / fontHeight, m_totalSize - 1); return line; } void MergeResultWindow::mousePressEvent(QMouseEvent* e) { m_bCursorOn = true; int xOffset = getTextXOffset(); int line = convertToLine(e->y()); QString s = getString(line); QTextLayout textLayout(s, font(), this); getTextLayoutForLine(line, s, textLayout); int pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x()); bool bLMB = e->button() == Qt::LeftButton; bool bMMB = e->button() == Qt::MidButton; bool bRMB = e->button() == Qt::RightButton; if((bLMB && (e->x() < xOffset)) || bRMB) // Fast range selection { m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = std::max(line, 0); int l = 0; MergeLineList::iterator i = m_mergeLineList.begin(); for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(l == line) break; l += i->mergeEditLineList.size(); if(l > line) break; } m_selection.reset(); // Disable current selection m_bCursorOn = true; setFastSelector(i); if(bRMB) { emit showPopupMenu(QCursor::pos()); } } else if(bLMB) // Normal cursor placement { pos = std::max(pos, 0); line = std::max(line, 0); if(e->QInputEvent::modifiers() & Qt::ShiftModifier) { if(!m_selection.isValidFirstLine()) m_selection.start(line, pos); m_selection.end(line, pos); } else { // Selection m_selection.reset(); m_selection.start(line, pos); m_selection.end(line, pos); } m_cursorXPos = pos; m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(pos)); if(m_pOptions->m_bRightToLeftLanguage) m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset); m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; update(); //showStatusLine( line, m_winIdx, m_pFilename, m_pDiff3LineList, m_pStatusBar ); } else if(bMMB) // Paste clipboard { pos = std::max(pos, 0); line = std::max(line, 0); m_selection.reset(); m_cursorXPos = pos; m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; pasteClipboard(true); } } void MergeResultWindow::mouseDoubleClickEvent(QMouseEvent* e) { if(e->button() == Qt::LeftButton) { int line = convertToLine(e->y()); QString s = getString(line); QTextLayout textLayout(s, font(), this); getTextLayoutForLine(line, s, textLayout); int pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x()); m_cursorXPos = pos; m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; if(!s.isEmpty()) { int pos1, pos2; calcTokenPos(s, pos, pos1, pos2, m_pOptions->m_tabSize); resetSelection(); m_selection.start(line, convertToPosOnScreen(s, pos1, m_pOptions->m_tabSize)); m_selection.end(line, convertToPosOnScreen(s, pos2, m_pOptions->m_tabSize)); update(); // emit selectionEnd() happens in the mouseReleaseEvent. } } } void MergeResultWindow::mouseReleaseEvent(QMouseEvent* e) { if(e->button() == Qt::LeftButton) { if(m_delayedDrawTimer) { killTimer(m_delayedDrawTimer); m_delayedDrawTimer = 0; } if(m_selection.isValidFirstLine()) { emit selectionEnd(); } } } void MergeResultWindow::mouseMoveEvent(QMouseEvent* e) { int line = convertToLine(e->y()); QString s = getString(line); QTextLayout textLayout(s, font(), this); getTextLayoutForLine(line, s, textLayout); int pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x()); m_cursorXPos = pos; m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; if(m_selection.isValidFirstLine()) { m_selection.end(line, pos); myUpdate(0); //showStatusLine( line, m_winIdx, m_pFilename, m_pDiff3LineList, m_pStatusBar ); // Scroll because mouse moved out of the window const QFontMetrics& fm = fontMetrics(); int fontWidth = fm.width('0'); int topLineYOffset = 0; int deltaX = 0; int deltaY = 0; if(!m_pOptions->m_bRightToLeftLanguage) { if(e->x() < getTextXOffset()) deltaX = -1; if(e->x() > width()) deltaX = +1; } else { if(e->x() > width() - 1 - getTextXOffset()) deltaX = -1; if(e->x() < fontWidth) deltaX = +1; } if(e->y() < topLineYOffset) deltaY = -1; if(e->y() > height()) deltaY = +1; m_scrollDeltaX = deltaX; m_scrollDeltaY = deltaY; if(deltaX != 0 || deltaY != 0) { emit scrollMergeResultWindow(deltaX, deltaY); } } } void MergeResultWindow::slotCursorUpdate() { m_cursorTimer.stop(); m_bCursorOn = !m_bCursorOn; if(isVisible()) { m_bCursorUpdate = true; const QFontMetrics& fm = fontMetrics(); int topLineYOffset = 0; int yOffset = (m_cursorYPos - m_firstLine) * fm.lineSpacing() + topLineYOffset; repaint(0, yOffset, width(), fm.lineSpacing() + 2); m_bCursorUpdate = false; } m_cursorTimer.start(500); } void MergeResultWindow::wheelEvent(QWheelEvent* e) { int d = -e->delta() * QApplication::wheelScrollLines() / 120; e->accept(); emit scrollMergeResultWindow(0, std::min(d, getNofVisibleLines())); } bool MergeResultWindow::event(QEvent* e) { if(e->type() == QEvent::KeyPress) { QKeyEvent* ke = static_cast(e); if(ke->key() == Qt::Key_Tab) { // special tab handling here to avoid moving focus keyPressEvent(ke); return true; } } return QWidget::event(e); } void MergeResultWindow::keyPressEvent(QKeyEvent* e) { int y = m_cursorYPos; MergeLineList::iterator mlIt; MergeEditLineList::iterator melIt; calcIteratorFromLineNr(y, mlIt, melIt); QString str = melIt->getString(this); int x = convertToPosInText(str, m_cursorXPos, m_pOptions->m_tabSize); QTextLayout textLayoutOrig(str, font(), this); getTextLayoutForLine(y, str, textLayoutOrig); bool bCtrl = (e->QInputEvent::modifiers() & Qt::ControlModifier) != 0; bool bShift = (e->QInputEvent::modifiers() & Qt::ShiftModifier) != 0; #ifdef Q_OS_WIN bool bAlt = (e->QInputEvent::modifiers() & Qt::AltModifier) != 0; if(bCtrl && bAlt) { bCtrl = false; bAlt = false; } // AltGr-Key pressed. #endif bool bYMoveKey = false; // Special keys switch(e->key()) { case Qt::Key_Escape: break; //case Key_Tab: break; case Qt::Key_Backtab: break; case Qt::Key_Delete: { if(deleteSelection2(str, x, y, mlIt, melIt)) break; if(!melIt->isEditableText()) break; if(x >= (int)str.length()) { if(y < m_totalSize - 1) { setModified(); MergeLineList::iterator mlIt1; MergeEditLineList::iterator melIt1; calcIteratorFromLineNr(y + 1, mlIt1, melIt1); if(melIt1->isEditableText()) { QString s2 = melIt1->getString(this); melIt->setString(str + s2); // Remove the line if(mlIt1->mergeEditLineList.size() > 1) mlIt1->mergeEditLineList.erase(melIt1); else melIt1->setRemoved(); } } } else { QString s = str.left(x); s += str.midRef(x + 1); melIt->setString(s); setModified(); } break; } case Qt::Key_Backspace: { if(deleteSelection2(str, x, y, mlIt, melIt)) break; if(!melIt->isEditableText()) break; if(x == 0) { if(y > 0) { setModified(); MergeLineList::iterator mlIt1; MergeEditLineList::iterator melIt1; calcIteratorFromLineNr(y - 1, mlIt1, melIt1); if(melIt1->isEditableText()) { QString s1 = melIt1->getString(this); melIt1->setString(s1 + str); // Remove the previous line if(mlIt->mergeEditLineList.size() > 1) mlIt->mergeEditLineList.erase(melIt); else melIt->setRemoved(); --y; x = str.length(); } } } else { QString s = str.left(x - 1); s += str.midRef(x); --x; melIt->setString(s); setModified(); } break; } case Qt::Key_Return: case Qt::Key_Enter: { if(!melIt->isEditableText()) break; deleteSelection2(str, x, y, mlIt, melIt); setModified(); QString indentation; if(m_pOptions->m_bAutoIndentation) { // calc last indentation MergeLineList::iterator mlIt1 = mlIt; MergeEditLineList::iterator melIt1 = melIt; for(;;) { const QString s = melIt1->getString(this); if(!s.isEmpty()) { int i; for(i = 0; i < s.length(); ++i) { if(s[i] != ' ' && s[i] != '\t') break; } if(i < s.length()) { indentation = s.left(i); break; } } // Go back one line if(melIt1 != mlIt1->mergeEditLineList.begin()) --melIt1; else { if(mlIt1 == m_mergeLineList.begin()) break; --mlIt1; melIt1 = mlIt1->mergeEditLineList.end(); --melIt1; } } } MergeEditLine mel(mlIt->id3l); // Associate every mel with an id3l, even if not really valid. mel.setString(indentation + str.mid(x)); if(x < (int)str.length()) // Cut off the old line. { // Since ps possibly points into melIt->str, first copy it into a temporary. QString temp = str.left(x); melIt->setString(temp); } ++melIt; mlIt->mergeEditLineList.insert(melIt, mel); x = indentation.length(); ++y; break; } case Qt::Key_Insert: m_bInsertMode = !m_bInsertMode; break; case Qt::Key_Pause: break; case Qt::Key_Print: break; case Qt::Key_SysReq: break; case Qt::Key_Home: x = 0; if(bCtrl) { y = 0; } break; // cursor movement case Qt::Key_End: x = INT_MAX; if(bCtrl) { y = INT_MAX; } break; case Qt::Key_Left: case Qt::Key_Right: if((e->key() == Qt::Key_Left) != m_pOptions->m_bRightToLeftLanguage) { if(!bCtrl) { int newX = textLayoutOrig.previousCursorPosition(x); if(newX == x && y > 0) { --y; x = INT_MAX; } else { x = newX; } } else { while(x > 0 && (str[x - 1] == ' ' || str[x - 1] == '\t')) { int newX = textLayoutOrig.previousCursorPosition(x); if(newX == x) break; x = newX; } while(x > 0 && (str[x - 1] != ' ' && str[x - 1] != '\t')) { int newX = textLayoutOrig.previousCursorPosition(x); if(newX == x) break; x = newX; } } } else { if(!bCtrl) { int newX = textLayoutOrig.nextCursorPosition(x); if(newX == x && y < m_totalSize - 1) { ++y; x = 0; } else { x = newX; } } else { while(x < (int)str.length() && (str[x] == ' ' || str[x] == '\t')) { int newX = textLayoutOrig.nextCursorPosition(x); if(newX == x) break; x = newX; } while(x < (int)str.length() && (str[x] != ' ' && str[x] != '\t')) { int newX = textLayoutOrig.nextCursorPosition(x); if(newX == x) break; x = newX; } } } break; case Qt::Key_Up: if(!bCtrl) { --y; bYMoveKey = true; } break; case Qt::Key_Down: if(!bCtrl) { ++y; bYMoveKey = true; } break; case Qt::Key_PageUp: if(!bCtrl) { y -= getNofVisibleLines(); bYMoveKey = true; } break; case Qt::Key_PageDown: if(!bCtrl) { y += getNofVisibleLines(); bYMoveKey = true; } break; default: { QString t = e->text(); if(t.isEmpty() || bCtrl) { e->ignore(); return; } else { if(bCtrl) { e->ignore(); return; } else { if(!melIt->isEditableText()) break; deleteSelection2(str, x, y, mlIt, melIt); setModified(); // Characters to insert QString s = str; if(t[0] == '\t' && m_pOptions->m_bReplaceTabs) { int spaces = (m_cursorXPos / m_pOptions->m_tabSize + 1) * m_pOptions->m_tabSize - m_cursorXPos; t.fill(' ', spaces); } if(m_bInsertMode) s.insert(x, t); else s.replace(x, t.length(), t); melIt->setString(s); x += t.length(); bShift = false; } } } } y = qBound(0, y, m_totalSize - 1); calcIteratorFromLineNr(y, mlIt, melIt); str = melIt->getString(this); x = qBound(0, x, (int)str.length()); int newFirstLine = m_firstLine; int newHorizScrollOffset = m_horizScrollOffset; if(y < m_firstLine) newFirstLine = y; else if(y > m_firstLine + getNofVisibleLines()) newFirstLine = y - getNofVisibleLines(); QTextLayout textLayout(str, font(), this); getTextLayoutForLine(m_cursorYPos, str, textLayout); // try to preserve cursor x pixel position when moving to another line if(bYMoveKey) { if(m_pOptions->m_bRightToLeftLanguage) x = textLayout.lineAt(0).xToCursor(m_cursorOldXPixelPos - (textLayout.position().x() - m_horizScrollOffset)); else x = textLayout.lineAt(0).xToCursor(m_cursorOldXPixelPos); } m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(x)); int hF = 1; // horizontal factor if(m_pOptions->m_bRightToLeftLanguage) { m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset); hF = -1; } int cursorWidth = 5; if(m_cursorXPixelPos < hF * m_horizScrollOffset) newHorizScrollOffset = hF * m_cursorXPixelPos; else if(m_cursorXPixelPos > hF * m_horizScrollOffset + getVisibleTextAreaWidth() - cursorWidth) newHorizScrollOffset = hF * (m_cursorXPixelPos - (getVisibleTextAreaWidth() - cursorWidth)); int newCursorX = x; if(bShift) { if(!m_selection.isValidFirstLine()) m_selection.start(m_cursorYPos, m_cursorXPos); m_selection.end(y, newCursorX); } else m_selection.reset(); m_cursorYPos = y; m_cursorXPos = newCursorX; // TODO if width of current line exceeds the current maximum width then force recalculating the scrollbars if(textLayout.maximumWidth() > getMaxTextWidth()) { m_maxTextWidth = qCeil(textLayout.maximumWidth()); emit resizeSignal(); } if(!bYMoveKey) m_cursorOldXPixelPos = m_cursorXPixelPos; m_bCursorOn = true; m_cursorTimer.start(500); update(); if(newFirstLine != m_firstLine || newHorizScrollOffset != m_horizScrollOffset) { emit scrollMergeResultWindow(newHorizScrollOffset - m_horizScrollOffset, newFirstLine - m_firstLine); return; } } void MergeResultWindow::calcIteratorFromLineNr( int line, MergeResultWindow::MergeLineList::iterator& mlIt, MergeResultWindow::MergeEditLineList::iterator& melIt) { for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; if(line > ml.mergeEditLineList.size()) { line -= ml.mergeEditLineList.size(); } else { for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { --line; if(line < 0) return; } } } } QString MergeResultWindow::getSelection() { QString selectionString; int line = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(m_selection.lineWithin(line)) { int outPos = 0; if(mel.isEditableText()) { const QString str = mel.getString(this); // Consider tabs for(int i = 0; i < str.length(); ++i) { int spaces = 1; if(str[i] == '\t') { spaces = tabber(outPos, m_pOptions->m_tabSize); } if(m_selection.within(line, outPos)) { selectionString += str[i]; } outPos += spaces; } } else if(mel.isConflict()) { selectionString += i18n(""); } if(m_selection.within(line, outPos)) { #ifdef Q_OS_WIN selectionString += '\r'; #endif selectionString += '\n'; } } ++line; } } return selectionString; } bool MergeResultWindow::deleteSelection2(QString& s, int& x, int& y, MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt) { if(m_selection.selectionContainsData()) { Q_ASSERT(m_selection.isValidFirstLine()); deleteSelection(); y = m_cursorYPos; calcIteratorFromLineNr(y, mlIt, melIt); s = melIt->getString(this); x = convertToPosInText(s, m_cursorXPos, m_pOptions->m_tabSize); return true; } return false; } void MergeResultWindow::deleteSelection() { if(!m_selection.selectionContainsData()) { return; } Q_ASSERT(m_selection.isValidFirstLine()); setModified(); int line = 0; MergeLineList::iterator mlItFirst; MergeEditLineList::iterator melItFirst; QString firstLineString; int firstLine = -1; int lastLine = -1; MergeLineList::iterator mlIt; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(mel.isEditableText() && m_selection.lineWithin(line)) { if(firstLine == -1) firstLine = line; lastLine = line; } ++line; } } if(firstLine == -1) { return; // Nothing to delete. } line = 0; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt, melIt1; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; melIt1 = melIt; ++melIt1; if(mel.isEditableText() && m_selection.lineWithin(line)) { QString lineString = mel.getString(this); int firstPosInLine = m_selection.firstPosInLine(line); int lastPosInLine = m_selection.lastPosInLine(line); if(line == firstLine) { mlItFirst = mlIt; melItFirst = melIt; int pos = convertToPosInText(lineString, firstPosInLine, m_pOptions->m_tabSize); firstLineString = lineString.left(pos); } if(line == lastLine) { // This is the last line in the selection int pos = convertToPosInText(lineString, lastPosInLine, m_pOptions->m_tabSize); firstLineString += lineString.midRef(pos); // rest of line melItFirst->setString(firstLineString); } if(line != firstLine || (m_selection.endPos() - m_selection.beginPos()) == lineString.length()) { // Remove the line if(mlIt->mergeEditLineList.size() > 1) mlIt->mergeEditLineList.erase(melIt); else melIt->setRemoved(); } } ++line; melIt = melIt1; } } m_cursorYPos = m_selection.beginLine(); m_cursorXPos = m_selection.beginPos(); m_cursorOldXPixelPos = m_cursorXPixelPos; m_selection.reset(); } void MergeResultWindow::pasteClipboard(bool bFromSelection) { //checking of m_selection if needed is done by deleteSelection no need for check here. deleteSelection(); setModified(); int y = m_cursorYPos; MergeLineList::iterator mlIt; MergeEditLineList::iterator melIt, melItAfter; calcIteratorFromLineNr(y, mlIt, melIt); melItAfter = melIt; ++melItAfter; QString str = melIt->getString(this); int x = convertToPosInText(str, m_cursorXPos, m_pOptions->m_tabSize); if(!QApplication::clipboard()->supportsSelection()) bFromSelection = false; QString clipBoard = QApplication::clipboard()->text(bFromSelection ? QClipboard::Selection : QClipboard::Clipboard); QString currentLine = str.left(x); QString endOfLine = str.mid(x); int i; int len = clipBoard.length(); for(i = 0; i < len; ++i) { QChar c = clipBoard[i]; if(c == '\r') continue; if(c == '\n') { melIt->setString(currentLine); MergeEditLine mel(mlIt->id3l); // Associate every mel with an id3l, even if not really valid. melIt = mlIt->mergeEditLineList.insert(melItAfter, mel); currentLine = ""; x = 0; ++y; } else { currentLine += c; ++x; } } currentLine += endOfLine; melIt->setString(currentLine); m_cursorYPos = y; m_cursorXPos = convertToPosOnScreen(currentLine, x, m_pOptions->m_tabSize); m_cursorOldXPixelPos = m_cursorXPixelPos; update(); } void MergeResultWindow::resetSelection() { m_selection.reset(); update(); } void MergeResultWindow::setModified(bool bModified) { if(bModified != m_bModified) { m_bModified = bModified; emit modifiedChanged(m_bModified); } } /// Saves and returns true when successful. bool MergeResultWindow::saveDocument(const QString& fileName, QTextCodec* pEncoding, e_LineEndStyle eLineEndStyle) { // Are still conflicts somewhere? if(getNrOfUnsolvedConflicts() > 0) { KMessageBox::error(this, i18n("Not all conflicts are solved yet.\n" "File not saved."), i18n("Conflicts Left")); return false; } if(eLineEndStyle == eLineEndStyleConflict || eLineEndStyle == eLineEndStyleUndefined) { KMessageBox::error(this, i18n("There is a line end style conflict. Please choose the line end style manually.\n" "File not saved."), i18n("Conflicts Left")); return false; } update(); FileAccess file(fileName, true /*bWantToWrite*/); if(m_pOptions->m_bDmCreateBakFiles && file.exists()) { bool bSuccess = file.createBackup(".orig"); if(!bSuccess) { KMessageBox::error(this, file.getStatusText() + i18n("\n\nCreating backup failed. File not saved."), i18n("File Save Error")); return false; } } QByteArray dataArray; QTextStream textOutStream(&dataArray, QIODevice::WriteOnly); if(pEncoding->name() == "UTF-8") textOutStream.setGenerateByteOrderMark(false); // Shouldn't be necessary. Bug in Qt or docs else textOutStream.setGenerateByteOrderMark(true); // Only for UTF-16 textOutStream.setCodec(pEncoding); int line = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(mel.isEditableText()) { QString str = mel.getString(this); if(line > 0) // Prepend line feed, but not for first line { if(eLineEndStyle == eLineEndStyleDos) { str.prepend("\r\n"); } else { str.prepend("\n"); } } textOutStream << str; ++line; } } } textOutStream.flush(); bool bSuccess = file.writeFile(dataArray.data(), dataArray.size()); if(!bSuccess) { KMessageBox::error(this, i18n("Error while writing."), i18n("File Save Error")); return false; } setModified(false); update(); return true; } QString MergeResultWindow::getString(int lineIdx) { MergeResultWindow::MergeLineList::iterator mlIt; MergeResultWindow::MergeEditLineList::iterator melIt; calcIteratorFromLineNr(lineIdx, mlIt, melIt); QString s = melIt->getString(this); return s; } bool MergeResultWindow::findString(const QString& s, int& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive) { int it = d3vLine; int endIt = bDirDown ? getNofLines() : -1; int step = bDirDown ? 1 : -1; int startPos = posInLine; for(; it != endIt; it += step) { QString line = getString(it); if(!line.isEmpty()) { int pos = line.indexOf(s, startPos, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); if(pos != -1) { d3vLine = it; posInLine = pos; return true; } startPos = 0; } } return false; } void MergeResultWindow::setSelection(int firstLine, int startPos, int lastLine, int endPos) { if(lastLine >= getNofLines()) { lastLine = getNofLines() - 1; QString s = getString(lastLine); endPos = s.length(); } m_selection.reset(); m_selection.start(firstLine, convertToPosOnScreen(getString(firstLine), startPos, m_pOptions->m_tabSize)); m_selection.end(lastLine, convertToPosOnScreen(getString(lastLine), endPos, m_pOptions->m_tabSize)); update(); } WindowTitleWidget::WindowTitleWidget(Options* pOptions) { m_pOptions = pOptions; setAutoFillBackground(true); QHBoxLayout* pHLayout = new QHBoxLayout(this); pHLayout->setMargin(2); pHLayout->setSpacing(2); m_pLabel = new QLabel(i18n("Output:")); pHLayout->addWidget(m_pLabel); m_pFileNameLineEdit = new QLineEdit(); pHLayout->addWidget(m_pFileNameLineEdit, 6); m_pFileNameLineEdit->installEventFilter(this); m_pFileNameLineEdit->setReadOnly(true); //m_pBrowseButton = new QPushButton("..."); //pHLayout->addWidget( m_pBrowseButton, 0 ); //connect( m_pBrowseButton, &QPushButton::clicked), this, &MergeResultWindow::slotBrowseButtonClicked); m_pModifiedLabel = new QLabel(i18n("[Modified]")); pHLayout->addWidget(m_pModifiedLabel); m_pModifiedLabel->setMinimumSize(m_pModifiedLabel->sizeHint()); m_pModifiedLabel->setText(""); pHLayout->addStretch(1); m_pEncodingLabel = new QLabel(i18n("Encoding for saving:")); pHLayout->addWidget(m_pEncodingLabel); m_pEncodingSelector = new QComboBox(); m_pEncodingSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents); pHLayout->addWidget(m_pEncodingSelector, 2); setEncodings(nullptr, nullptr, nullptr); m_pLineEndStyleLabel = new QLabel(i18n("Line end style:")); pHLayout->addWidget(m_pLineEndStyleLabel); m_pLineEndStyleSelector = new QComboBox(); m_pLineEndStyleSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents); pHLayout->addWidget(m_pLineEndStyleSelector); setLineEndStyles(eLineEndStyleUndefined, eLineEndStyleUndefined, eLineEndStyleUndefined); } void WindowTitleWidget::setFileName(const QString& fileName) { m_pFileNameLineEdit->setText(QDir::toNativeSeparators(fileName)); } QString WindowTitleWidget::getFileName() { return m_pFileNameLineEdit->text(); } //static QString getLineEndStyleName( e_LineEndStyle eLineEndStyle ) //{ // if ( eLineEndStyle == eLineEndStyleDos ) // return "DOS"; // else if ( eLineEndStyle == eLineEndStyleUnix ) // return "Unix"; // return QString(); //} void WindowTitleWidget::setLineEndStyles(e_LineEndStyle eLineEndStyleA, e_LineEndStyle eLineEndStyleB, e_LineEndStyle eLineEndStyleC) { m_pLineEndStyleSelector->clear(); QString dosUsers; if(eLineEndStyleA == eLineEndStyleDos) dosUsers += i18n("A"); if(eLineEndStyleB == eLineEndStyleDos) dosUsers += QLatin1String(dosUsers.isEmpty() ? "" : ", ") + i18n("B"); if(eLineEndStyleC == eLineEndStyleDos) dosUsers += QLatin1String(dosUsers.isEmpty() ? "" : ", ") + i18n("C"); QString unxUsers; if(eLineEndStyleA == eLineEndStyleUnix) unxUsers += i18n("A"); if(eLineEndStyleB == eLineEndStyleUnix) unxUsers += QLatin1String(unxUsers.isEmpty() ? "" : ", ") + i18n("B"); if(eLineEndStyleC == eLineEndStyleUnix) unxUsers += QLatin1String(unxUsers.isEmpty() ? "" : ", ") + i18n("C"); m_pLineEndStyleSelector->addItem(i18n("Unix") + (unxUsers.isEmpty() ? QString("") : QLatin1String(" (") + unxUsers + QLatin1String(")"))); m_pLineEndStyleSelector->addItem(i18n("DOS") + (dosUsers.isEmpty() ? QString("") : QLatin1String(" (") + dosUsers + QLatin1String(")"))); e_LineEndStyle autoChoice = (e_LineEndStyle)m_pOptions->m_lineEndStyle; if(m_pOptions->m_lineEndStyle == eLineEndStyleAutoDetect) { if(eLineEndStyleA != eLineEndStyleUndefined && eLineEndStyleB != eLineEndStyleUndefined && eLineEndStyleC != eLineEndStyleUndefined) { if(eLineEndStyleA == eLineEndStyleB) autoChoice = eLineEndStyleC; else if(eLineEndStyleA == eLineEndStyleC) autoChoice = eLineEndStyleB; else autoChoice = eLineEndStyleConflict; //conflict (not likely while only two values exist) } else { e_LineEndStyle c1, c2; if(eLineEndStyleA == eLineEndStyleUndefined) { c1 = eLineEndStyleB; c2 = eLineEndStyleC; } else if(eLineEndStyleB == eLineEndStyleUndefined) { c1 = eLineEndStyleA; c2 = eLineEndStyleC; } else /*if( eLineEndStyleC == eLineEndStyleUndefined )*/ { c1 = eLineEndStyleA; c2 = eLineEndStyleB; } if(c1 == c2 && c1 != eLineEndStyleUndefined) autoChoice = c1; else autoChoice = eLineEndStyleConflict; } } if(autoChoice == eLineEndStyleUnix) m_pLineEndStyleSelector->setCurrentIndex(0); else if(autoChoice == eLineEndStyleDos) m_pLineEndStyleSelector->setCurrentIndex(1); else if(autoChoice == eLineEndStyleConflict) { m_pLineEndStyleSelector->addItem(i18n("Conflict")); m_pLineEndStyleSelector->setCurrentIndex(2); } } e_LineEndStyle WindowTitleWidget::getLineEndStyle() { int current = m_pLineEndStyleSelector->currentIndex(); if(current == 0) return eLineEndStyleUnix; else if(current == 1) return eLineEndStyleDos; else return eLineEndStyleConflict; } void WindowTitleWidget::setEncodings(QTextCodec* pCodecForA, QTextCodec* pCodecForB, QTextCodec* pCodecForC) { m_pEncodingSelector->clear(); // First sort codec names: std::map names; QList mibs = QTextCodec::availableMibs(); foreach(int i, mibs) { QTextCodec* c = QTextCodec::codecForMib(i); if(c != nullptr) names[QLatin1String(c->name())] = c; } if(pCodecForA) m_pEncodingSelector->addItem(i18n("Codec from A: %1", QLatin1String(pCodecForA->name())), QVariant::fromValue((void*)pCodecForA)); if(pCodecForB) m_pEncodingSelector->addItem(i18n("Codec from B: %1", QLatin1String(pCodecForB->name())), QVariant::fromValue((void*)pCodecForB)); if(pCodecForC) m_pEncodingSelector->addItem(i18n("Codec from C: %1", QLatin1String(pCodecForC->name())), QVariant::fromValue((void*)pCodecForC)); std::map::iterator it; for(it = names.begin(); it != names.end(); ++it) { m_pEncodingSelector->addItem(it->first, QVariant::fromValue((void*)it->second)); } m_pEncodingSelector->setMinimumSize(m_pEncodingSelector->sizeHint()); if(pCodecForC && pCodecForB && pCodecForA) { if(pCodecForA == pCodecForB) m_pEncodingSelector->setCurrentIndex(2); // C else if(pCodecForA == pCodecForC) m_pEncodingSelector->setCurrentIndex(1); // B else m_pEncodingSelector->setCurrentIndex(2); // C } else if(pCodecForA && pCodecForB) m_pEncodingSelector->setCurrentIndex(1); // B else m_pEncodingSelector->setCurrentIndex(0); } QTextCodec* WindowTitleWidget::getEncoding() { return (QTextCodec*)m_pEncodingSelector->itemData(m_pEncodingSelector->currentIndex()).value(); } void WindowTitleWidget::setEncoding(QTextCodec* pEncoding) { int idx = m_pEncodingSelector->findText(QLatin1String(pEncoding->name())); if(idx >= 0) m_pEncodingSelector->setCurrentIndex(idx); } //void WindowTitleWidget::slotBrowseButtonClicked() //{ // QString current = m_pFileNameLineEdit->text(); // // QUrl newURL = KFileDialog::getSaveUrl( current, 0, this, i18n("Select file (not saving yet)")); // if ( !newURL.isEmpty() ) // { // m_pFileNameLineEdit->setText( newURL.url() ); // } //} void WindowTitleWidget::slotSetModified(bool bModified) { m_pModifiedLabel->setText(bModified ? i18n("[Modified]") : ""); } bool WindowTitleWidget::eventFilter(QObject* o, QEvent* e) { if(e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) { QPalette p = m_pLabel->palette(); QColor c1 = m_pOptions->m_fgColor; QColor c2 = Qt::lightGray; if(e->type() == QEvent::FocusOut) c2 = m_pOptions->m_bgColor; p.setColor(QPalette::Window, c2); setPalette(p); p.setColor(QPalette::WindowText, c1); m_pLabel->setPalette(p); m_pEncodingLabel->setPalette(p); m_pEncodingSelector->setPalette(p); } if(o == m_pFileNameLineEdit && e->type() == QEvent::Drop) { QDropEvent* d = static_cast(e); if(d->mimeData()->hasUrls()) { QList lst = d->mimeData()->urls(); if(lst.count() > 0) { static_cast(o)->setText(lst[0].toString()); static_cast(o)->setFocus(); return true; } } } return false; } //#include "mergeresultwindow.moc" diff --git a/test/alignmenttest.cpp b/test/alignmenttest.cpp index ac79cee..32dbb77 100644 --- a/test/alignmenttest.cpp +++ b/test/alignmenttest.cpp @@ -1,500 +1,500 @@ // vim:sw=3:ts=3:expandtab #include #include #include #include #include #include "diff.h" #include "gnudiff_diff.h" #include "options.h" #include "progress.h" #define i18n(s) s bool verbose = false; Options *m_pOptions = NULL; ManualDiffHelpList m_manualDiffHelpList; bool g_bIgnoreWhiteSpace = true; bool g_bIgnoreTrivialMatches = true; void printDiffList(const QString caption, const DiffList &diffList) { QTextStream out(stdout); DiffList::const_iterator i; out << "Printing difflist " << caption << ":" << endl; out << " nofEquals, diff1, diff2" << endl; for(i = diffList.begin(); i != diffList.end(); i++) { out << " " << i->nofEquals << "," << i->diff1 << "," << i->diff2 << endl; } } void printDiff3List(const Diff3LineList &diff3LineList, const SourceData &sd1, const SourceData &sd2, const SourceData &sd3, bool forceVerbosity=false) { const int columnsize = 30; const int linenumsize = 6; Diff3LineList::const_iterator i; for ( i=diff3LineList.begin(); i!=diff3LineList.end(); ++i ) { QTextStream out(stdout); QString lineAText; QString lineBText; QString lineCText; const Diff3Line& d3l = *i; if(d3l.lineA != -1) { const LineData *pLineData = &sd1.getLineDataForDiff()[d3l.lineA]; lineAText = QString(pLineData->pLine, pLineData->size); lineAText.replace(QString("\r"), QString("\\r")); lineAText.replace(QString("\n"), QString("\\n")); lineAText = QString("%1 %2").arg(d3l.lineA, linenumsize).arg(lineAText.left(columnsize - linenumsize - 1)); } if(d3l.lineB != -1) { const LineData *pLineData = &sd2.getLineDataForDiff()[d3l.lineB]; lineBText = QString(pLineData->pLine, pLineData->size); lineBText.replace(QString("\r"), QString("\\r")); lineBText.replace(QString("\n"), QString("\\n")); lineBText = QString("%1 %2").arg(d3l.lineB, linenumsize).arg(lineBText.left(columnsize - linenumsize - 1)); } if(d3l.lineC != -1) { const LineData *pLineData = &sd3.getLineDataForDiff()[d3l.lineC]; lineCText = QString(pLineData->pLine, pLineData->size); lineCText.replace(QString("\r"), QString("\\r")); lineCText.replace(QString("\n"), QString("\\n")); lineCText = QString("%1 %2").arg(d3l.lineC, linenumsize).arg(lineCText.left(columnsize - linenumsize - 1)); } out << QString("%1 %2 %3").arg(lineAText, -columnsize) .arg(lineBText, -columnsize) .arg(lineCText, -columnsize); if(verbose || forceVerbosity) { - out << " " << d3l.bAEqB << " " << d3l.bBEqC << " " << d3l.bAEqC; + out << " " << d3l.isEqualAB() << " " << d3l.isEqualBC() << " " << d3l.isEqualAC(); } out << endl; } } void printDiff3List(QString caption, const Diff3LineList &diff3LineList, const SourceData &sd1, const SourceData &sd2, const SourceData &sd3, bool forceVerbosity=false) { QTextStream out(stdout); out << "Printing diff3list " << caption << ":" << endl; printDiff3List(diff3LineList, sd1, sd2, sd3, forceVerbosity); } void determineFileAlignment(SourceData &m_sd1, SourceData &m_sd2, SourceData &m_sd3, Diff3LineList &m_diff3LineList) { DiffList m_diffList12; DiffList m_diffList23; DiffList m_diffList13; m_diff3LineList.clear(); // Run the diff. if ( m_sd3.isEmpty() ) { runDiff( m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_diffList12,1,2, &m_manualDiffHelpList, m_pOptions); calcDiff3LineListUsingAB( &m_diffList12, m_diff3LineList ); fineDiff( m_diff3LineList, 1, m_sd1.getLineDataForDisplay(), m_sd2.getLineDataForDisplay() ); } else { runDiff( m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_diffList12,1,2, &m_manualDiffHelpList, m_pOptions); runDiff( m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_sd3.getLineDataForDiff(), m_sd3.getSizeLines(), m_diffList23,2,3, &m_manualDiffHelpList, m_pOptions); runDiff( m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd3.getLineDataForDiff(), m_sd3.getSizeLines(), m_diffList13,1,3, &m_manualDiffHelpList, m_pOptions); if (verbose) { printDiffList("m_diffList12", m_diffList12); printDiffList("m_diffList23", m_diffList23); printDiffList("m_diffList13", m_diffList13); } calcDiff3LineListUsingAB( &m_diffList12, m_diff3LineList ); if (verbose) printDiff3List("after calcDiff3LineListUsingAB", m_diff3LineList, m_sd1, m_sd2, m_sd3); calcDiff3LineListUsingAC( &m_diffList13, m_diff3LineList ); if (verbose) printDiff3List("after calcDiff3LineListUsingAC", m_diff3LineList, m_sd1, m_sd2, m_sd3); correctManualDiffAlignment( m_diff3LineList, &m_manualDiffHelpList ); calcDiff3LineListTrim( m_diff3LineList, m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff(), &m_manualDiffHelpList ); if (verbose) printDiff3List("after 1st calcDiff3LineListTrim", m_diff3LineList, m_sd1, m_sd2, m_sd3); if ( m_pOptions->m_bDiff3AlignBC ) { calcDiff3LineListUsingBC( &m_diffList23, m_diff3LineList ); if (verbose) printDiff3List("after calcDiff3LineListUsingBC", m_diff3LineList, m_sd1, m_sd2, m_sd3); correctManualDiffAlignment( m_diff3LineList, &m_manualDiffHelpList ); calcDiff3LineListTrim( m_diff3LineList, m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff(), &m_manualDiffHelpList ); if (verbose) printDiff3List("after 2nd calcDiff3LineListTrim", m_diff3LineList, m_sd1, m_sd2, m_sd3); } fineDiff( m_diff3LineList, 1, m_sd1.getLineDataForDisplay(), m_sd2.getLineDataForDisplay() ); fineDiff( m_diff3LineList, 2, m_sd2.getLineDataForDisplay(), m_sd3.getLineDataForDisplay() ); fineDiff( m_diff3LineList, 3, m_sd3.getLineDataForDisplay(), m_sd1.getLineDataForDisplay() ); } calcWhiteDiff3Lines( m_diff3LineList, m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff() ); } QString getLineFromSourceData(const SourceData &sd, int line) { const LineData *pLineData = &sd.getLineDataForDiff()[line]; QString lineText = QString(pLineData->pLine, pLineData->size); lineText.replace(QString("\r"), QString("\\r")); lineText.replace(QString("\n"), QString("\\n")); return lineText; } void loadExpectedAlignmentFile(QString expectedResultFileName, Diff3LineList &expectedDiff3LineList) { Diff3Line d3l; expectedDiff3LineList.clear(); QFile file(expectedResultFileName); QString line; if ( file.open(QIODevice::ReadOnly) ) { QTextStream t( &file ); while ( !t.atEnd() ) { QStringList lst = t.readLine().split(QRegExp("\\s+")); d3l.lineA = lst.at(0).toInt(); d3l.lineB = lst.at(1).toInt(); d3l.lineC = lst.at(2).toInt(); expectedDiff3LineList.push_back( d3l ); } file.close(); } } void writeActualAlignmentFile(QString actualResultFileName, const Diff3LineList &actualDiff3LineList) { Diff3LineList::const_iterator p_d3l; QFile file(actualResultFileName); if ( file.open(QIODevice::WriteOnly) ) { { QTextStream t( &file ); for(p_d3l = actualDiff3LineList.begin(); p_d3l != actualDiff3LineList.end(); p_d3l++) { t << p_d3l->lineA << " " << p_d3l->lineB << " " << p_d3l->lineC << endl; } } file.close(); } } bool dataIsConsistent(int line1, QString &line1Text, int line2, QString &line2Text, bool equal) { bool consistent = false; if(line1 == -1 || line2 == -1) { consistent = !equal; } else { /* If the equal boolean is true the line content must be the same, * if the line content is different the boolean should be false, * but other than that we can't be sure: * - if the line content is the same the boolean may not be true because * GNU diff may have put that line as a removal in the first file and * an addition in the second. * - also the comparison this test does between lines considers all * whitespace equal, while GNU diff doesn't (for instance U+0020 vs U+00A0) */ if(equal) { consistent = (line1Text == line2Text); } else if (line1Text != line2Text) { consistent = !equal; } else { consistent = true; } } return consistent; } bool runTest(QString file1, QString file2, QString file3, QString expectedResultFile, QString actualResultFile, int maxLength) { Options options; Diff3LineList actualDiff3LineList, expectedDiff3LineList; QTextCodec *p_codec = QTextCodec::codecForName("UTF-8"); QTextStream out(stdout); options.m_bIgnoreCase = false; options.m_bPreserveCarriageReturn = false; options.m_bDiff3AlignBC = true; m_pOptions = &options; SourceData m_sd1, m_sd2, m_sd3; QString msgprefix = "Running test with "; QString filepattern = QString(file1).replace("_base.", "_*."); QString msgsuffix = QString("...%1").arg("", maxLength - filepattern.length()); out << msgprefix << filepattern << msgsuffix; if(verbose) { out << endl; } out.flush(); m_sd1.setOptions(&options); m_sd1.setFilename(file1); m_sd1.readAndPreprocess(p_codec, false); m_sd2.setOptions(&options); m_sd2.setFilename(file2); m_sd2.readAndPreprocess(p_codec, false); m_sd3.setOptions(&options); m_sd3.setFilename(file3); m_sd3.readAndPreprocess(p_codec, false); determineFileAlignment(m_sd1, m_sd2, m_sd3, actualDiff3LineList); loadExpectedAlignmentFile(expectedResultFile, expectedDiff3LineList); Diff3LineList::iterator p_actual = actualDiff3LineList.begin(); Diff3LineList::iterator p_expected = expectedDiff3LineList.begin(); bool equal = true; bool sequenceError = false; bool consistencyError = false; equal = (actualDiff3LineList.size() == expectedDiff3LineList.size()); int latestLineA = -1; int latestLineB = -1; int latestLineC = -1; while(equal && (p_actual != actualDiff3LineList.end())) { /* Check if all line numbers are in sequence */ if(p_actual->lineA != -1) { if(p_actual->lineA <= latestLineA) { sequenceError = true; } else { latestLineA = p_actual->lineA; } } if(p_actual->lineB != -1) { if(p_actual->lineB <= latestLineB) { sequenceError = true; } else { latestLineB = p_actual->lineB; } } if(p_actual->lineC != -1) { if(p_actual->lineC <= latestLineC) { sequenceError = true; } else { latestLineC = p_actual->lineC; } } /* Check if the booleans that indicate if lines are equal are consistent with the content of the lines */ QString lineAText = (p_actual->lineA == -1) ? "" : getLineFromSourceData(m_sd1, p_actual->lineA).simplified().replace(" ", ""); QString lineBText = (p_actual->lineB == -1) ? "" : getLineFromSourceData(m_sd2, p_actual->lineB).simplified().replace(" ", ""); QString lineCText = (p_actual->lineC == -1) ? "" : getLineFromSourceData(m_sd3, p_actual->lineC).simplified().replace(" ", ""); - if(!dataIsConsistent(p_actual->lineA, lineAText, p_actual->lineB, lineBText, p_actual->bAEqB)) + if(!dataIsConsistent(p_actual->lineA, lineAText, p_actual->lineB, lineBText, p_actual->isEqualAB())) { if(verbose) out << "inconsistency: line " << p_actual->lineA << " of A vs line " << p_actual->lineB << " of B" << endl; consistencyError = true; } - if(!dataIsConsistent(p_actual->lineB, lineBText, p_actual->lineC, lineCText, p_actual->bBEqC)) + if(!dataIsConsistent(p_actual->lineB, lineBText, p_actual->lineC, lineCText, p_actual->isEqualBC())) { if(verbose) out << "inconsistency: line " << p_actual->lineB << " of B vs line " << p_actual->lineC << " of C" << endl; consistencyError = true; } - if(!dataIsConsistent(p_actual->lineA, lineAText, p_actual->lineC, lineCText, p_actual->bAEqC)) + if(!dataIsConsistent(p_actual->lineA, lineAText, p_actual->lineC, lineCText, p_actual->isEqualAC())) { if(verbose) out << "inconsistency: line " << p_actual->lineA << " of A vs line " << p_actual->lineC << " of C" << endl; consistencyError = true; } /* Check if the actual output of the algorithm is equal to the expected output */ equal = (p_actual->lineA == p_expected->lineA) && (p_actual->lineB == p_expected->lineB) && (p_actual->lineC == p_expected->lineC); p_actual++; p_expected++; } if(sequenceError) { out << "NOK" << endl; out << "Actual result has incorrectly sequenced line numbers:" << endl; out << "----------------------------------------------------------------------------------------------" << endl; printDiff3List(actualDiff3LineList, m_sd1, m_sd2, m_sd3); } else if(consistencyError) { out << "NOK" << endl; out << "Actual result has inconsistent equality booleans:" << endl; out << "----------------------------------------------------------------------------------------------" << endl; printDiff3List(actualDiff3LineList, m_sd1, m_sd2, m_sd3, true); } else if(equal) { out << "OK" << endl; } else { out << "NOK" << endl; writeActualAlignmentFile(actualResultFile, actualDiff3LineList); out << "Actual result (written to " << actualResultFile << "):" << endl; out << "----------------------------------------------------------------------------------------------" << endl; printDiff3List(actualDiff3LineList, m_sd1, m_sd2, m_sd3); out << "----------------------------------------------------------------------------------------------" << endl; out << "Expected result:" << endl; out << "----------------------------------------------------------------------------------------------" << endl; printDiff3List(expectedDiff3LineList, m_sd1, m_sd2, m_sd3); out << "----------------------------------------------------------------------------------------------" << endl; } return equal; } QStringList gettestdatafiles(QString testdir) { QStringList baseFilePaths; QTextStream out(stdout); QStringList nameFilter; nameFilter << "*_base.*"; QDir testdatadir(testdir); QStringList baseFileNames = testdatadir.entryList(nameFilter, QDir::Files, QDir::Name); QListIterator file_it(baseFileNames); while(file_it.hasNext()) { baseFilePaths.append(testdir + "/" + file_it.next()); } out << testdir << ": " << baseFilePaths.size() << " files" << endl; QStringList subdirs = testdatadir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name); QListIterator dir_it(subdirs); while (dir_it.hasNext()) { QString subdir = dir_it.next(); QStringList subdirBaseFilePaths = gettestdatafiles(testdir + "/" + subdir); baseFilePaths.append(subdirBaseFilePaths); } return baseFilePaths; } int main(int argc, char *argv[]) { bool allOk = true; int maxLength = 0; QTextStream out(stdout); QDir testdatadir("testdata"); /* Print data at various steps in the algorithm to get an idea where to look for the root cause of a failing test */ if((argc == 2) && (!strcmp(argv[1], "-v"))) { verbose = true; } QStringList baseFiles = gettestdatafiles("testdata"); QListIterator it(baseFiles); for (int i = 0; i < baseFiles.size(); i++) { maxLength = std::max(baseFiles.at(i).length(), maxLength); } maxLength += testdatadir.path().length() + 1; while (it.hasNext()) { QString fileName = it.next(); QRegExp baseFileRegExp("(.*)_base\\.(.*)"); baseFileRegExp.exactMatch(fileName); QString prefix = baseFileRegExp.cap(1); QString suffix = baseFileRegExp.cap(2); QString contrib1FileName(prefix + "_contrib1." + suffix); QString contrib2FileName(prefix + "_contrib2." + suffix); QString expectedResultFileName(prefix + "_expected_result." + suffix); QString actualResultFileName(prefix + "_actual_result." + suffix); if(QFile(contrib1FileName).exists() && QFile(contrib2FileName).exists() && QFile(expectedResultFileName).exists()) { bool ok = runTest(fileName, contrib1FileName, contrib2FileName, expectedResultFileName, actualResultFileName, maxLength); allOk = allOk && ok; } else { out << "Skipping " << fileName << " " << contrib1FileName << " " << contrib2FileName << " " << expectedResultFileName << " " << endl; } } out << (allOk ? "All OK" : "Not all OK") << endl; return allOk ? 0 : -1; }